Commit graph

23 commits

Author SHA1 Message Date
顾盼
5f4b60d2a0
fix(cua): align input and session repairs with 0.27.0 (#15784)
* fix(cua): correct background input and pending native cleanup

* chore(cua): prepare aligned 0.26.1 release

* fix(cua): discover live accessory apps outside install roots

* fix(cua): Return pending actions when popup windows hide

* fix(cua): Avoid unbound Windows input value probes

* fix(cua): Check macOS class factories on the class

* fix(cua): Verify app text insertion through accessibility

* fix(cua): Route macOS editing keys to their AX hosts

* fix(cua): Preserve owner windows and public refusal recovery

* docs(cua): Complete the 0.26.1 repair release scope
2026-10-30 14:04:52 +08:00
顾盼
4d050906e5
feat(cua): enable default cursor and live macOS PiP (#15669)
* feat(cua): enable native cursor and live macOS previews

* fix(cua): preserve focus in standalone UI hosts

* feat(cua): polish floating preview cards

* fix(cua): stabilize PiP hover and modal interactions

* test(node-repl): use a lint-compatible status assertion

* fix(cua): retain the preview while a newer frame is pending

* fix(cua): use an auxiliary fullscreen role for the preview

* fix(cua): preserve worker lifetime and preview controls
2026-10-29 17:37:27 +08:00
Shaojin Wen
249ab50d84
fix(node-repl): let the kernel exit promptly on shutdown (#15160)
* fix(node-repl): let the kernel exit promptly on shutdown

The kernel reads its input pipe through a file stream, and a pending
read on that pipe keeps process.exit() from completing until the pipe
reaches EOF. Since the manager began waiting 2.5 s for a graceful exit
before signalling, every reset and disposal cost about 3 seconds and
ended in SIGTERM, which also slowed the node-repl suite from ~35 s to
~3.8 min in CI.

End the pipe right after the shutdown frame so the kernel can exit as
soon as its cleanup finishes.

* fix(node-repl): stop session processes when the kernel exits on its own

Ending the kernel's input pipe lets it exit within milliseconds, so the
group signals that used to reach processes the session started without
detaching were no longer sent: they kept running after reset, disposal
and a cancellation timeout.

When the kernel exits on its own, signal its process group the way the
signalled shutdown did: SIGTERM, then SIGKILL if anything is still in the
group after 500 ms. The group keeps its ID while any member lives, and a
kernel that was already gone before termination began is left alone.

Tests start a child from session code that holds a socket to the test,
so its exit is seen even when nothing reaps it, and cover reset,
disposal, a timed-out cancellation barrier and a child that ignores
SIGTERM. The idle-kernel test now checks that no signal reaches a
running kernel rather than that no signal is sent at all.

* fix(node-repl): stop leftover session processes after a SIGTERM exit too

The process group stop ran only when the kernel exited within the first
grace period. A kernel whose own cleanup outlasts it (a slow Computer
Use disposal, say) exits during the SIGTERM wait instead, so neither the
group stop nor the SIGKILL ran, and a session process that ignores
SIGTERM kept running. Before the pipe was closed that kernel could not
finish exiting, so the old path always reached SIGKILL.

Run the group stop whenever the kernel exits before the hard stop. A
test blocks Computer Use disposal past the grace period and checks that
a SIGTERM-ignoring child is still stopped.

* test(node-repl): pin the SIGTERM step of the leftover-process sweep

The slow-cleanup case was named for a kernel that exits on SIGTERM, but
the kernel ignores SIGTERM once shutdown has started; it exits when the
fixture's cleanup ends. Rename it and assert the group got SIGTERM while
the kernel was still running, so a kernel that exits in the first wait
no longer passes for this path.

Add a case whose session process handles SIGTERM by writing a marker and
exiting, so replacing the SIGTERM-then-SIGKILL sweep with a bare SIGKILL
fails a test.
2026-10-23 17:14:58 +00:00
顾盼
a791e47a54
feat(cua): streamline computer use and port native reliability fixes (#14846)
* feat(cua): preload the JavaScript computer use runtime

* refactor(cua): remove the native Wayland backend

* feat(cua): move app computer use into the driver

* feat(cua): preserve background delivery and port platform fixes

* feat(cua): add explicit desktop input and browser runtime facade

* feat(cua): package the unified 0.26.0 computer use runtime

* fix(cua): finalize cross-platform native reliability

* fix(cua): isolate runtime releases from CLI versioning

* fix(cua): keep Electron out of root workspace installs

* fix(cua): synchronize consumers and async cleanup tests

* fix(browser): migrate smoke to preloaded cua runtime

* docs(cua): document MCP setup for Qwen Code Claude Code and Codex
2026-10-21 14:03:31 +08:00
易良
2cfd390650
feat(computer-use): let a remote session use your desktop through a node_repl relay (#11799)
* docs(plans): add remote computer use plan for a local Mac

Plan for letting a Qwen Code session on a headless Linux dev box operate
the user's local Mac through computer use: the principle, the existing
pieces, three options, and a Mac companion that joins the reverse tool
channel.

* docs(plans): add handoff and slice-0 verification brief for remote computer use

Add a handoff document for the session or agent that continues this
work, and a real-Mac verification brief for slice 0. Update the plan
with a newly found pitfall: on macOS the driver's LaunchServices
auto-launch does not forward environment variables, so an auto-launched
daemon never enables the HTTP MCP endpoint.

* docs(plans): replace the driver relay with a node_repl relay for remote computer use

Rereading main shows the bundled computer-use skill never talks to the
qwen-cua-driver daemon: ComputerUse.create() embeds the driver inside the
node_repl process, and a standalone daemon rejects trusted sessions from
any connection that is not its embedding parent (serve.rs:787, error 77).
That invalidates the v1 plan's option B and makes options A and C relay
the wrong layer (the raw tool surface meant for external agents).

The plan now relays node_repl itself over the reverse tool channel, keeps
the skill's App API intact, and lists the one skill change this needs
(read references with read_file instead of inside node_repl). The
verification brief is rewritten around a ten-minute fact check on a real
Mac plus the slice-1 prototype acceptance; the handoff document follows.

* docs(plans): make the Mac relay a qwen subcommand, not a standalone app

Maintainer decision: the Mac-side relay for remote computer use is a
qwen subcommand (qwen mac-bridge) run from a terminal. That matches how
local computer use already works, where qwen launches node_repl from the
terminal and the TCC grants are recorded against the terminal, so it
adds no install step and no new permission identity.

The plan drops the signed-app, bundle-id and menu-bar requirements, makes
Ctrl-C the local stop control, and demotes the signed host to an optional
slice that is off by default. The handoff document records the decision
so the next session does not reopen it, and the verification brief uses
the subcommand throughout.

* docs(plans): name the relay qwen bridge and drop the macOS-only framing

The relay is for any machine with a graphical session, not for macOS:
it only moves MCP frames, the SDK reports the connected platform, and
the skill already carries per-platform references. macOS is the platform
verified in this round because the TCC facts were read from its code
path. The subcommand is qwen bridge, one new top-level command in
packages/cli/src/commands/bridge.ts; the plan file is renamed from
mac-companion to desktop-relay and the handoff and verification brief
follow.

* feat(computer-use): let a remote session use this desktop through a node_repl relay

A Qwen Code session on a headless machine can now drive the desktop the
user is sitting at. That computer lends its own node_repl (with
@qwen-code/cua-sdk and the embedded driver) to one remote session:

- `node-repl-mcp desktop-relay install` puts the runtime under
  ~/.qwen/desktop-relay and registers a launchd socket on 127.0.0.1:47821
  in inetd mode, so nothing runs until something connects.
- The Web Shell gains a "Use this computer" footer entry. It hands the
  daemon URL, session and token to the relay, which asks for approval in
  a native dialog, starts node_repl and registers it for that session
  over the daemon's reverse tool channel. A dropped connection ends the
  relay; control never resumes without a fresh approval.
- Terminal sessions reach the same socket through an SSH RemoteForward;
  their first tools/call asks for approval.

One node_repl serves the daemon's several MCP clients: the relay answers
repeated initialize requests from the first result, rewrites request ids
and turns replies above the 10 MB frame cap into errors. The skill needs
no change; main already reads references with read_file when node_repl
cannot.

Not built, typechecked or tested locally; CI and the real-Mac brief in
docs/verification/remote-computer-use/ are the verification.

* chore(deps): record ws in the pnpm lockfile for node-repl-mcp

The pnpm lockfile is generated from package-lock.json by
`corepack pnpm import`; the previous commit updated only the npm one,
so frozen installs failed on the node-repl importer's new ws and
@types/ws specifiers. Both resolve to the versions the cli importer
already records.

* fix(node-repl): drop the console override in the desktop relay agent

The repo lints packages with no-console, and the override guarded against
output nothing on the agent path produces.

* fix(node-repl): restore the desktop relay agent entry point

The previous commit spliced runtime.ts at stale offsets and left it
unparseable. This applies the intended change to the last good copy: no
console override and no unused format import.

* fix: harden remote computer-use relay

* fix(node-repl): forward unambiguous cancellations through the desktop relay

Stopping a turn in the remote session sends notifications/cancelled for
the running tools/call. The relay dropped every cancellation because the
reverse channel does not say which multiplexed MCP client sent it, so a
node_repl cell kept driving the desktop after the user had stopped the
agent. Forward a cancellation, with its request id rewritten, when exactly
one pending request carries that id; still drop it when none or several
do.

* docs(computer-use): refresh the remote desktop relay handoff for another machine

Switch the verification setup to the pnpm install main now uses, add a
check that stopping a turn stops a running desktop cell, and add a
troubleshooting guide that walks the chain from browser to launchd to
daemon to macOS permissions. The handoff records the current state, the
two decisions still open, and why SSH workspaces do not replace this.

* docs(computer-use): record the Linux-side verification results for the desktop relay

The Mac-side end-to-end run is still blocked: this machine has no GUI and no
reachable macOS host. What could be verified here is recorded instead - the
relay's real HTTP surface over a genuine fd-0 socket with launchd's inetd
activation emulated (B.1-B.4 plus the edge cases curl cannot express), and a
code-level walk of the cancellation chain the 2026-09-23 fix relies on.

Two new findings: a failing osascript is indistinguishable from an explicit
Deny and leaves nothing in agent.log, and launchd's default ThrottleInterval
may serialize these short-lived per-connection processes, so it should be
measured first on the Mac.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(web-shell): gate desktop relay and surface browser permission

* fix(computer-use): keep desktop relay usable after cancellation

* fix(computer-use): complete remote desktop relay acceptance

* fix(node-repl): harden desktop relay consent, lifecycle, and cancellation paths (#11799)

- skill: probe the desktop tool with `in` so the code-mode tools proxy
  does not abort the script before the fallback runs
- agent: bound the raw JSON-RPC buffer (64 KiB, matching the HTTP path)
  and retire tools/call cancelled while the consent dialog is open
- launchd: roll back the plist when bootstrap fails; confirm bootout
  actually unloaded the agent before deleting its plist
- consent: log dialog failures to stderr instead of reporting them as a
  human denial with no trace
- mcp-child-relay: forward an ambiguous-id cancellation to every match
  instead of dropping it, clamp relayed yield_time_ms under the daemon's
  30 s frame budget, and never let a throwing child reject handle()
- docs: replace the overlay merge recipe with a three-way merge, scope
  the tccutil reset guidance, and align the cancellation check with the
  channel timeout

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(node-repl): bound desktop relay connection failures

* fix(web-shell): preserve active desktop relay revocation

* docs(computer-use): correct setup and redact verification details

* fix(desktop-relay): close review security gaps

* fix(desktop-relay): harden process ownership and prewarm

* fix(desktop-relay): pin the setup command to a published node-repl version

DESKTOP_RELAY_INSTALL_COMMAND, packages/node-repl/README.md and
docs/users/features/computer-use.md all handed out
`npx -y @qwen-code/node-repl-mcp@latest desktop-relay install`. The
published `latest` (0.1.6) has no `desktop-relay` subcommand and its
entrypoint never reads argv, so it connects a stdio MCP server and blocks
on stdin: zero output, nothing installed, no launchd socket. The Web Shell
panel then re-serves that same string behind a Copy button in exactly the
`missing` phase the command produced, so the loop has no exit.

Pin the spec to the package version the way the repo already does for this
package (packages/core/src/skills/bundled/browser-use/skill.test.ts), and
bump packages/node-repl 0.1.6 -> 0.1.7 in this PR so the publish carries
the relay. Without the bump, cd-cua-driver.yml rejects the release with
"already exists with different integrity" and the pinned version would
still lack the subcommand. Sync the three version lines the release
validate-version gate reads (computer-use SKILL.md, browser-use SKILL.md,
computer-use user guide) plus the hard pin in
scripts/tests/release-workflow.test.js.

desktop-relay-client.test.ts now fails when the constant drifts from
packages/node-repl/package.json or reintroduces `@latest`.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(desktop-relay): stop reporting relay state that was never confirmed

Five review findings shared one cause: the Web Shell panel and the launchd
teardown each asserted a state they had not actually confirmed.

- deriveDesktopRelayStatus let another session's live relay mask this
  session's connect failure, because input.error was only consulted after
  the other-session return. The error now wins over other-session; a relay
  that is live for this session still outranks it, since that is the truth.
- awaitingApproval and error were component-global while the probe was
  scoped by sessionId and daemonUrl, so switching session or daemon showed
  the previous one's pending dialog or stale error. Both are reset when
  either changes.
- retainLiveDesktopRelayProbe kept a live observation for any non-ready
  probe without limit, so a relay that vanished stayed "connected" for as
  long as the panel was mounted. Retention now expires after three
  inconclusive probes, which still covers a transient blip.
- disconnectDesktopRelay resolved void and never read the response status,
  so a revocation that never reached the relay was indistinguishable from
  one that did, and disconnect() cleared the probe unconditionally. It now
  reports success only on an ok response, and the panel keeps its last
  observation until the relay confirms, leaving Disconnect retryable.
- currentUid() returned 0 under sudo, so uninstall queried gui/0 while the
  agent was loaded in the invoking user's domain. The existing "still
  loaded" guard in uninstallLaunchAgent therefore could not see the agent
  and deleted the plist out from under it. SUDO_UID now supplies the
  domain; Number() rather than parseInt() rejects a malformed value
  instead of truncating it into a wrong uid.

Addresses review findings R3-5, R3-6, R3-7, R2-10 and R3-14. Each has a
regression test that was verified to fail when its fix is reverted.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(desktop-relay): cancel stale approvals and bound registration

* fix(desktop-relay): keep revocation available when unreachable

* fix(desktop-relay): serialize consent and allow cold runtime startup

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-09-28 06:43:22 +00:00
易良
bcea19780f
fix(node-repl): widen the bounded-cancellation test margin under runner load (#12884)
The "recovers from ... within a bounded cancellation interval" cases armed
the host-side exec timeout at 200ms. The kernel crosses two await points
(the prepended dynamic import and the case's own 1ms timer) before it
reaches the blocking payload, and the vm evaluate watchdog is released at
the first suspension, so only the host can bound the wait. When a loaded
shared runner delayed the kernel past those 200ms, the cancel frame was
handled while the kernel still idled at an await point: the kernel
cancelled cleanly and survived, so the run reported status "timeout" with
kernelReplaced=false and the replacement assertion failed.

Raise the timeout margin for the host-driven timeout variants to 2000ms
and the case budget to 20000ms so the kernel is reliably inside the
blocked payload before the cancel frame arrives. The assertions and the
cancellation-boundary behavior under test are unchanged.
2026-09-28 02:33:23 +00:00
tanzhenxin
b7543aeb1b
feat(browser-use): add Chrome Native Messaging relay (#11242)
* feat(browser-use): add Playwright browser SDK

* docs(browser-use): document browser use architecture

* fix(browser-use): make browser input actions reliable

* feat(browser-use): preserve screenshot coordinate metadata

* fix(browser-use): recover stale sockets across VM realms

* fix(browser-use): bound locator action retries

* fix(browser-use): keep page input in the background

* fix(browser-use): reject ambiguous input arguments

* fix(browser-use): validate locator composition identity

* fix(browser-use): recover socket locks across VM realms

* fix(browser-use): preserve input results across navigation

* fix(browser-use): capture screenshots through bounded CDP frames

* fix(browser-use): separate input and navigation waits

* feat(browser-use): add Chrome native relay

* fix(chrome-extension): avoid foreground tabs and stale cursor markup

* fix(chrome-extension): require Chrome 125 for flat sessions

* feat(browser-use): add product integration

* test(browser-use): add managed Chrome smoke coverage

* feat(node-repl): expose structured image metadata

* test(browser-use): preserve complete smoke reports

* feat(browser-use): bundle skill and runtime with CLI

* fix(browser-use): install pinned Playwright in fresh workspaces

* feat(browser-use): require explicit skill opt-in

* feat(browser-use): make bundled skill available by default

Keep explicit skill disables authoritative, update documentation and tests, and use the latest Node REPL package in setup instructions.

* fix(ci): build browser-use before Live Host publish

Build the bundled browser runtime before core in the Live Host npm dependency chain, and cover that prerequisite order with a regression test.

* fix(browser-use): output screenshot metadata explicitly

* fix(node-repl): preserve image metadata outside prose budget

* fix(browser-use): address review round on workspace wiring and runtime correctness

- register the workspace in .pnpmfile.mjs, pnpm-lock.yaml, and the
  unit-vitest-configs registry, with the shared-pool timeout ternary and
  off-Linux unhandled-error exemption (fixes the red Install/Test checks)
- map Playwright's 'warning' console type to the contract's 'warn' level
- reject captures whose pixel dimensions contradict the css-pixels
  coordinate contract instead of certifying rescaled images
- keep one unattachable derived tab from failing tabs.list while still
  propagating a lost session
- clear the cached dialog when Chrome resolves it out of band or the page
  navigates, guarded by dialog identity so a newer dialog survives
- synthesize a parseable Browser.getVersion (numeric Chrome major, host
  platform token) so Playwright enables the right keyboard editing commands
- keep YAML-quoted snapshot keys and link props in interactive listings and
  stop claiming subtrees that filtering removed
- always release the mouse button when a drag gesture fails

* fix(dev): build browser-use runtime before startup

* fix(browser-use): isolate cleanup and correct smoke checks

* fix(browser-use): resolve managed Chrome through Playwright

* fix(browser-use): harden native relay and tab lifecycle

* fix(browser-use): drain host output and persist restored tab state

* fix(browser-use): scope dialog handles and clean navigation waiters

* fix(browser-use): preserve input outcomes and release modifiers

* fix(browser-use): drain session teardown and release debugger attachments

* fix(browser-use): correct locator validation and evaluation

* fix(dev): reuse prebuilt Browser Use runtime

* fix(browser-use): address review round on locator, bridge, and screenshot correctness

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(browser-use): require installed extension before native host setup

* fix(browser-use): address review round on schema probe bound and regex validation

* fix(browser-use): start transport before Chrome preflight

* fix(browser-use): exclude staged runtime from code checks

* fix(core): declare Browser Use build dependency

* fix(browser-use): preserve successful navigation and dialog input

* fix(browser-use): harden input drain and tab registration cleanup

* fix(browser-use): keep sessions bound to one Chrome profile

* fix(browser-use): bound commands and validate evaluation JSON

* fix(browser-use): reuse unchanged Native Host registration

* fix(browser-use): wait for fresh extension installation

* fix(browser-use): address review round on screenshot origin, dialog scope, and bridge privacy

- screenshot: tolerate settle-probe failure (measure the ratio from the
  captured pixels, re-capture on HiDPI), measure layout after the settle
  wait, and reject screencast frames whose scroll offsets disagree with
  the published origin
- session: clear the cached dialog only on main-frame navigation;
  abort tab attachment when the tab is removed mid-attach
- errors: classify crashes and timeouts by Playwright's structured error
  name instead of page-influenced message text
- locator.press: release chord modifiers when a later token is rejected;
  cap locator.type at the 60k characters its per-character budget can
  deliver inside the 120s ceiling
- snapshot: keep AI-mode [cursor=pointer] generic nodes in the
  interactive listing
- bridge: derive the fallback socket directory as an env-independent
  per-user 0700 subdirectory on all POSIX hosts and verify ownership
  before binding

* fix(browser-use): close review-critical gaps in error mapping, budgets, and downloads

- errors: classify by Playwright error name first; text phrases match only
  the start of the first message line so page-controlled text (log tails,
  quoted markup, page-thrown values) cannot pick the reported code, and a
  TimeoutError name wins over crash-shaped page text
- transport: cdp.send bridge requests default to 130s so operations up to
  the 120s schema ceiling report their own deadline; other requests keep
  the 30s default
- transport: the /tmp fallback socket directory is a per-user leaf directly
  under the sticky root (no shared intermediate a first mover could own);
  ensureSocketDirectory re-verifies the leaf after creation and walks
  ancestors up to the sticky root, requiring each to be owned by this user
  or root and not group/other-writable
- runtime: withModifiers no longer rewrites a completed action into a
  failure when a modifier release fails; chord failure cleanup releases the
  chord's own tokens (including non-modifiers) instead of four fixed names
- playwright: enable CDP download events on tab attach (Playwright's
  noDefaults connect leaves them off, so waitForEvent('download') could
  never resolve); locator.downloadMedia races the caller deadline, aborts
  the in-page fetch at it, resolves media contained in a located wrapper
  before an ancestor link, restricts URL schemes, and names CORS rejections
  distinctly; locator.allTextContents waits for the first match within the
  caller budget and still resolves [] when nothing attaches
- screenshot: full-page captures get a 16 Mi-pixel budget (1920x3000 pages
  now succeed) and the encoded byte ceiling drops to the 4 MiB model image
  limit so captures are not dropped at the model boundary
- snapshot: interactiveOnly promotes only a terminal [cursor=pointer]
  attribute (page text quoting the marker no longer promotes the line), and
  truncation skips over-long lines instead of discarding the rest
- schemas: reject empty string/regex matchers and exact:true combined with
  a regex matcher (Playwright silently drops exact for regexes)
- tests: the SDK evaluate fixture now mirrors production serialization;
  POSIX socket tests carry win32 gates; posix.join keeps the derived socket
  path a pure function of uid and platform on every host

* fix(browser-use): close review criticals in error mapping, chords, downloads, and snapshots

- errors: classify Playwright's genuine strict-mode violation, which
  crosses CDP as the raw exception description and reaches the client
  behind an "Error: " layer ("locator.click: Error: strict mode
  violation: ... resolved to N elements:"); requiring the element count
  keeps a page-thrown lookalike from forging LOCATOR_NOT_UNIQUE
- runtime-helpers: on a failed chord press, release the tokens
  Playwright actually derived ('+' separates only after a non-empty
  token, so a standalone '+' is a literal key) rather than the caller's
  array elements; an element embedding '+' previously released nothing
  and left modifiers held. locator.press uses the same tokenizer
- locator.downloadMedia: resolve an ordered candidate list (element,
  media descendants, anchor descendants, closest ancestor) and keep the
  first candidate that exposes a URL, so a wrapping anchor no longer
  beats the image it contains and a source srcset or an unloaded
  video's source child can supply the URL
- snapshot: unwrap Playwright's single-quoted YAML key before the
  [cursor=pointer] suffix test, so clickable nodes whose accessible
  name forces key quoting stay in the interactive listing
- docs: align the Input completion contract with the shipped modifier
  cleanup semantics (cleanup failures are discarded; only the action's
  own error propagates) in both languages

* fix(browser-use): close round-13 review criticals in errors, srcset, evaluateAll, snapshots, and screenshot origin

* fix(browser-use): point the pnpm importer at a vitest snapshot that exists

Merging main brought in #11625, which regenerated pnpm-lock.yaml and
dropped the vitest@3.2.7 snapshot keyed with @vitest/browser and
supports-color@8.1.1. The packages/browser-use importer still referenced
that key, so every Install leg failed with
ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY. Re-key the importer to the snapshot
its sibling packages already use; nothing else in the lockfile changes.

* fix(browser-use): close round-14 criticals in error classification, dialog ordering and screenshot ratio

R13-2: the strict-mode branch of sanitizeOperationError was the only
text-phrase branch not gated on the evaluate channel, so a page-thrown
primitive "strict mode violation: ... resolved to 2 elements:" picked
LOCATOR_NOT_UNIQUE. On that channel only Playwright's "Error: "-wrapped
form classifies now; other channels keep the unwrapped form.

R14-1: Playwright hands a dialog over on a later turn than the bridge
reports its CDP events (a microtask in CRSession plus the in-process
dispatcher's setImmediate), so an open+close pair delivered in one chunk
left a phantom dialog that blocked the tab. Each tab now traces dialog
openings and closes in bridge order and drops a delivered dialog the
bridge already reported closed; a close with no traced opening (a dialog
open before the tab was attached) is never charged to a later dialog, and
the microtask clear stays for the delivered case.

R14-2: window.devicePixelRatio is page script, and a spoofed value became
an unbounded clip.scale while the pixel re-measure only ran when the
probe failed. The probed ratio is now trusted only within 0.25..8, and the
measure-and-recapture correction runs whenever the first fallback capture
disagrees with the requested CSS region, deriving the real ratio from
Chrome's own pixels (measured / (width * scale)).

Also resolve the temp root in the ensureSocketDirectory tests: the
ancestor sweep rejects symlinks, and macOS reaches os.tmpdir() through
/var -> /private/var, so the tests failed locally on macOS.

* fix(browser-use): tear down accepted peers on start failure and pin the transport's untested paths

The esbuild banner declared require/__filename/__dirname/process bindings
the bundle never uses, so any bundled module declaring one of them built
green but failed to load; drop it (and the unused .wasm loader) and import
dist/index.js after bundling so an unloadable bundle fails the build.

When start() fails after listen (chmod, handshake), a peer validated in
between kept server.close() waiting forever; destroy and clear the
accepted sockets first, mirroring stop(). net.Server has no
closeAllConnections(), so that is the whole fix.

Tests now cover the QWEN_BROWSER_USE_SOCKET_PATH override, a hello with a
mismatched protocol version or extension id, onConnectionChange on the
real transport, an in-flight request when the extension disconnects and
the configured request budget, the ok:false error-code mapping,
FrameDecoder split-frame reassembly and its length ceiling, the
start-failure teardown, and all five schema refine predicates.

* fix(browser-use): mirror the contract bounds in the SDK and state the documented limits

nth() now rejects indexes outside the contract's -10000..10000 before
dispatch, and all() fails closed when the plan already has 32 steps or
the match count exceeds what nth() can address, instead of handing back
locators that only fail on first use. Both evaluate script builders
normalise method-shorthand and class-method sources the way Playwright
does (compile-only vm.Script probe, then a function prefix), so those
well-typed callbacks no longer die as a page-side SyntaxError.

providerTab clamps title and url to the claimTab schema's 20000-char cap
so the documented openTabs() -> claimTab() round trip survives long URLs,
and orderOpenTabs() establishes the http(s)-only, newest-first listing
the model documentation promises. downloadMedia derives the file name
from the URL path only for http(s) sources and leaves blob:/data: names
to the browser.

The model-facing documentation now states the 1s read default, that
dom_cua.scroll's x/y are wheel deltas rather than coordinates, and that
a cross-origin resource without CORS headers cannot be downloaded from
the page origin; the design docs record that boundary as follow-up work.
Tests pin every one of these, plus buildLocator's frame, combinator,
filter and positional branches, the file-chooser SDK surface, and the
claimTab argument guards.

* fix(browser-use): keep background tab registration from moving the selection

registerTab selected every tab it registered, and two of its callers are
background paths (derivedTabTracked, syncDerivedTabs via tabs.list), so
a read-only tabs.list could move tabs.selected onto a popup. Selection is
now set only by newTab and claimTab from the info they return.

Also: tabs.get is allowed while a dialog is open, matching tabs.list and
tabs.selected, which already return the dialog-safe TabInfo; the
auxiliary-button click moves Playwright's tracked cursor before sending
the CDP button events so a later wheel lands where the click did; the
dead providerTabId lookup is removed; openTabs applies orderOpenTabs.

Tests: the runtime test file mocks node:timers so vi.useFakeTimers()
actually controls the input drain bound (the 250ms test drops to ~0ms),
and new cases pin the drain clearTimeout, the registration probe's
session detach, promptText pass-through, tabs.get under a dialog, the
openTabs/claimTab fail-closed guards and ordering, popup adoption from
derivedTabTracked, the abandoned navigation waiter's swallowed rejection,
the uploadFiles path guards, and the screencast fallback and byte budget
on the unconstrained capture path.

* test(browser-use): pin the element bindings the runtime gives evaluate scripts

The SDK emits scripts that read the free variables `element` and
`elements`, and the runtime binds them when it compiles the script inside
the page; no test ran either page-side callback, so swapping the binding
name on one side kept the suite green while every locator.evaluate would
throw a ReferenceError in production. Run the real callbacks with fake
elements so the contract is pinned from both ends.

* fix(browser-use): fail closed on page-controlled strict-mode text and prefer contained media over anchor hrefs

* fix(browser-use): bound page title reads and download a located anchor's file over its icon

* fix(browser-use): correct media downloads and frame composition

* fix(browser-use): bound locator.count and allTextContents by the caller deadline

* fix(browser-use): preserve native snapshots for DOM CUA

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(browser-use): classify a closed tab from the tab's own state

The pinned playwright-core client renders a closed target as a plain Error:
only TimeoutError sets `name`, so the `name === 'TargetClosedError'` branch
in sanitizeOperationError never fired, and because evaluate-channel text
fails closed by design, a tab that closed or crashed while a page command
ran was reported as OPERATION_FAILED instead of the documented STALE_TAB.

Decide tab-gone in the dispatcher from `tab.stale` and `page.isClosed()`,
which the page observers keep current, before the text classifier runs.
Drop the dead name branch and replace the test that fabricated the name
with one built the way the client builds the error, plus dispatch-level
cases for both signals that fail without the dispatcher check.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(browser-use): restore the kernel realm process binding and land the round-5 review batch

Criticals from review round 5, each reproduced before fixing:

- The bundled skill imports the staged runtime inside the node_repl kernel,
  whose untrusted vm context has no `process` global and denies the
  `node:process` specifier. 3fc2dc3e10 removed the bundle banner that bound
  `process` through createRequire, so the skill's first cell threw
  `ReferenceError: process is not defined`; the build's own load check runs
  in the main realm and could not see it. Restore the binding on the index
  build and add a kernel-backed test that runs SKILL.md's first cell through
  the real kernel and fails on the ReferenceError (R5-4).
- Bump @qwen-code/node-repl-mcp to 0.1.6: this PR changes its published
  source (image metadata channel) and the package is excluded from the
  repo-wide bump, so the release lane's integrity check would fail. Pin the
  browser-use skill to that version, have the skill test assert the pin
  equals the package version, and extend the cua-driver release gate to
  cross-check the browser-use pin (R5-2, R3-5).
- Report an incompatible extension as EXTENSION_VERSION_MISMATCH so
  browser discovery surfaces the update guidance instead of an empty list
  after the connect budget (R5-5).
- Make tab grouping non-fatal in tabs.create, matching its siblings, so a
  cosmetic grouping failure no longer closes the tab just created (R5-6).

Review suggestions verified still applying, with tests proven red under
mutation: typecheck the scripts directory; pin the Native Messaging chunk
boundary and envelope; return installedPaths from the installer; treat a
blank install-home override as unset; warn on a skipped foreign manifest;
refuse a foreign launcher with the refusals' missing red arms; keep the
preflight download waiter handled; settle the parked-profile test; name
the private-directory cause in the bridge start error; declare the finalize
status union once; reject unknown or closed kept tabs; pin the extension's
protocol constants to the SDK source; resolve the extension test root from
the module; drop the duplicated permission assertion; make the group
barrier real; read the overlay style rules; map a vanished tab to STALE_TAB
in the relay; clamp listed tab text to the schema bound; remove the
unreachable onDetach child-session branch; assert the raced tab in the
disconnect test; cover the history scheme filter and session-name reset;
and fix the extension-source and prerequisite wording in the docs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(browser-use): let the SauceDemo smoke recognize equivalent skill usage

The model-driven smoke completed the task correctly on every run but its
transcript checks asserted one exact spelling: the literal runtime entry
path in the model's code, and one parent-scoped price selector with an
exact number of dollar values in the cell's output. A model that builds
the entry from the skill root across cells, scopes the price locator
differently, or echoes its chosen items after the read failed the smoke.

Key the import check on what every spelling shares: the built-in skill
root the CLI substitutes into SKILL.md, the entry file name and the setup
call. Match price reads on the price class, prefer a read that printed
exactly the expected count, take the leading set otherwise, and take the
last such read for the cart since the flow reads it after the inventory.
The smoke-checks test covers all three import spellings, a foreign runtime
and a missing setup call, and the parser's scoped, echoed and non-list
reads. Verified against a real Chromium 151 run: all six checks true.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(browser-use): close six fail-open paths in the playwright adapter

Round-22 review found six Critical defects in the new package; each is
fixed at its root and pinned by a test that goes red without the fix:

- locator.evaluateAll opened a second full timeout window after the
  attach wait had consumed the caller's budget; the wait and the read
  now share one deadline, and a wait that consumed the budget settles
  [] without evaluating.
- tab.goto passed any string to page.goto, so a file:// URL would have
  handed local file contents to the tab's evaluate and snapshot reads;
  goto is gated to http(s) before the navigation runs.
- Synthesized CDP error responses carried no code, so a reply orphaned
  by a target crash tripped Playwright's internal assert and tore down
  the whole transport; they now carry -32001, the orphan-swallow code.
- A dialog already open when a tab attaches is replayed by neither
  Chrome nor Playwright, so the DIALOG_OPEN gate stayed blind while the
  modal blocked the renderer; registration now probes the renderer and
  gates the tab on an unanswered probe, with getJsDialog reporting it
  under a stable handle whose accept/dismiss run over CDP.
- A dialog opening Playwright never delivered stayed in the trace
  forever and swallowed the charge of every later dialog on the tab; a
  closed, undelivered entry is retired once the dispatcher turn has
  passed.
- snapshotRefLocator validated only the ref's shape, so a ref from a
  pre-navigation snapshot could act on whichever element the new
  document happened to hand the same string; refs are bound to the
  snapshot that emitted them and cleared on main-frame navigation.

Co-Authored-By: Qwen-Coder <qwen-coder@alibabacloud.com>

* build: exempt the staged Browser Use skill runtime from the dependency-direction check

The browser-use package build stages the bundled skill's runtime, node_modules
included, under packages/core/src/skills/bundled/browser-use/runtime (git-ignored),
and the TUI dependency-direction gate fails on any node_modules inside a protected
root. Declare that staged, ignored root as a build artifact so the gate skips it
without a diagnostic; every other skipped-directory name still fails.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MWe8PKNMpsKdQruTWkvFFc

* build: leave the staged Browser Use runtime out of the dependency-direction walk entirely

The staged root is a build artifact, not source: skipping only its
node_modules while still parsing the SDK bundle next to it was inconsistent.
Do not walk anything under a declared staged root; every other node_modules
inside the protected roots remains a diagnostic.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MWe8PKNMpsKdQruTWkvFFc

* build(browser-use): stage the skill runtime only for the bundle and for dev mode

The package build used to copy the bundled skill runtime, node_modules
included, into packages/core/src, which every built tree then carried and
which the TUI dependency-direction gate rightly rejects. Follow the qc-helper
convention instead: the bundle step stages the runtime into dist/bundled for
the shipped CLI, and `npm run dev` stages the git-ignored development copy
next to the source SKILL.md, because only dev mode reads bundled skills from
the source tree. No build step writes into src any more, so the gate
exemption added earlier is dropped and the check script returns to main's
version. The kernel-realm test stages its own copy into a temporary skill
directory rather than depending on a build side effect.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MWe8PKNMpsKdQruTWkvFFc

* fix(browser-use): deliver bridge messages in arrival order and keep dev info flags fast

Three fixes from the round-3/4 verification and review round 7–8:

- Transport: a response only settles a promise, so its consumer ran on the
  microtask queue while events from the same socket chunk were emitted
  synchronously and overtook it. Playwright installs a page's renderer and
  lifecycle listeners inside `Page.getFrameTree().then(...)`, so an
  overtaking `Runtime.executionContextCreated` found no listener and a
  claimed tab lost its main world for good (B1). Inbound messages now go
  through one per-socket queue whose drain yields a macrotask after
  settling a response, so every continuation runs before the next message;
  chunks arriving meanwhile join the same queue. Regression:
  `delivers a response ahead of the events that share its socket chunk`.
- Relay: `requireControllableTab` judged a popup by `tab.url`, which is
  empty until the navigation commits, while the claim keyed on
  `pendingUrl`; the runtime's immediate `tabs.get` therefore rejected the
  tab the relay had just reported (R8-2). It now judges by
  `pendingUrl || url`, like the claim. Regression:
  `a popup known only by its pending URL stays controllable`.
- dev launcher: the dev-mode runtime staging skips `--version`/`--help`
  (and `-v`/`-h`), restoring the fast path the existing tests pin (R7-1).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MWe8PKNMpsKdQruTWkvFFc

* test(browser-use): make the smoke evidence and the persist-before-connect pin honest

Round 6–7 review findings on the verification tooling:

- The SauceDemo smoke decided "the staged runtime was loaded" from three
  substring tests over all cells joined, so a run whose staged import threw
  and fell back to a foreign entry still passed (R6-1). It now looks at the
  import expressions per cell: a literal path under the staged runtime, or a
  path assembled from the substituted skill root, in a cell that reaches the
  setup export; a literal entry loaded from anywhere else disqualifies the
  run.
- Price reads were attributed to the cart by count and read order, so an
  inventory sorted low-to-high passed as a cart read and the fallback to the
  first read never returned null (R6-2). Reads are now attributed by the
  tab.url() confirmation of cart.html the prompt requires: inventory before
  it, cart after it, and no cart read without it.
- The persist-before-connect invariant was asserted inside the mock's
  postMessage, which the bridge calls under a bare catch, so the reordered
  mutant stayed green (R7-2). The mock now records every hello with the
  identity persisted at that moment and the test asserts the order outside
  the bridge; the mutant is red.
- SKILL.md states the node-repl-mcp 0.1.6 requirement next to the
  emitImage step the agent always reads, with how to detect and re-register
  an older server (R6-3).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MWe8PKNMpsKdQruTWkvFFc

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.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>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 05:50:24 +00:00
顾盼
b8fea284fd
fix(cua): make Linux input delivery adaptive (#11876) 2026-09-14 15:34:26 +00:00
Dragon
ba0ddcd0b7
fix(cua): preserve Linux observations and REPL diagnostics (#11829)
* fix(cua): preserve Linux observations and REPL diagnostics

* fix(node-repl): bound unresponsive cancellation
2026-09-14 14:40:33 +00:00
顾盼
6a211b4652
chore(release): bump node repl to 0.1.4 (#11771)
Some checks failed
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Lint & Static (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / web-shell E2E Smoke (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (macos-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 / Integration Tests (no-AK, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (ubuntu-22.04) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (windows-2022) (push) Blocked by required conditions
E2E Tests / Build for E2E (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 1/2 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 2/2 (push) Blocked by required conditions
E2E Tests / E2E Interactive - OpenTUI renderer (bun) (push) Blocked by required conditions
E2E Tests / channel-plugin E2E (nightly) (push) Blocked by required conditions
E2E Tests / cron-interactive E2E (nightly) (push) Blocked by required conditions
E2E Tests / web-shell Browser Regression (push) Waiting to run
pnpm Worktree Smoke / Install (macos-latest) (push) Waiting to run
pnpm Worktree Smoke / Install (ubuntu-latest) (push) Waiting to run
pnpm Worktree Smoke / Install (windows-latest) (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
npm cache producer / Save npm cache (push) Has been cancelled
2026-09-13 10:38:51 +00:00
Dragon
e6647b3a3b
fix(cua): bound observations and reduce model round trips (#11735)
* fix(cua): retain bounded observation baselines and cap text

* perf(cua): reduce avoidable computer-use round trips

* perf(node-repl): avoid repeated runtime rules in tool discovery

* fix(cua): recognize attached sheets in exact-target validation

* fix(cua): expose known refusal categories in app errors

* perf(cua): Combine macOS bootstrap with initial app observation

* perf(cua): Prefer exact selection for field replacement

* perf(cua): Lead field edits with verified text selection

* chore(cua): reconcile local optimizations with updated App routing
2026-09-13 09:12:48 +00:00
tanzhenxin
2eaed75010
fix(cua): address tool failures and efficiency in OSWorld runs (#11683)
* fix(cua): address tool failures and efficiency in OSWorld runs

* docs(cua): consolidate efficiency bugfix design notes
2026-09-13 09:12:47 +00:00
Dragon
4ec9f63d0a
feat(core): add CodeModeOnly tool execution (#10607)
* feat(core): add CodeModeOnly tool execution

* fix(core): preserve CodeModeOnly execution semantics

* fix(core): drop the tool_search reminder in CodeModeOnly

CodeModeOnly hides `tool_search` and binds every deferred tool — schema
included — into the `exec` description, so progressive discovery has
nothing left to do. The session prelude still announced those tools as
"reachable via `tool_search`", pointing the model at a tool that is
never declared and re-billing the text on every cached prefix. Worse,
tools already callable as `tools.web_fetch(...)` looked gated behind a
lookup step that does not exist.

The subagent and fork-resume callers already opted out, but the
main-session prelude sites did not, so the gate now lives where the
prelude is built rather than in each caller.

* chore: refresh settings schema

* chore: refresh companion notices

* fix: register exec tool metadata

* fix(cli): localize exec tool display name

* fix(core): stop offering ToolSearch lookups in CodeModeOnly

CodeModeOnly hides tool_search and binds every deferred schema into the
exec description, so text that tells the model to look a tool up
describes a surface it cannot reach. Return an empty deferred summary
from the registry itself - this also covers the fork-resume reminder
that bypassed the earlier call-site gate - and point the image zoom
hint at tools.zoom_image.

* feat(core): teach the system prompt CodeModeOnly's tool surface

In CodeModeOnly the model can only call exec, so the tool guidance and the
worked examples described a surface it does not have: bare tool names it
cannot call, an instruction to issue several tool calls in one response, and
examples in a direct tool-call syntax that does not exist there.

Give "Using Your Tools" a code-mode branch that routes every tool through
tools.<name>, states which tools are direct controls instead, and replaces
multi-call parallelism with batching inside one exec program. Swap the four
model-family example sets for one shared exec set, since the syntax
differences they exist for do not apply. The mechanics of exec itself stay in
its tool description; the prompt carries only policy.

* test: include code mode host in standalone fixture

* fix(core): return code mode media as multimodal output

* feat(core): support image tool results in code mode

* fix(core): carry deferred tool schemas in the exec description

CodeModeOnly hides tool_search and never surfaces a nested call as a history
functionCall, so a signature collapsed to Record<string, unknown> could never
be filled in later: parameter names were reachable only by guessing, or by
reading them off validation errors one at a time. Deferred tools keep their
registry state; only the generated signature gains the schema.

* fix(node-repl): restore the bin exec bit after rebuild

npm chmods dist/index.js when it links the bin at install time, but the
build deletes that exact file and tsc re-emits it as 0644. After any
rebuild node_modules/.bin/node-repl-mcp therefore points at a
non-executable target and spawning it fails with EACCES (exit 126)
before the shebang is ever read.

OR the exec bits into the emitted mode instead of setting 0o755, so a
restrictive umask is preserved rather than widened.

* fix(cli): pin patched Ink version

* feat(cli): add collapsible tool call details

* fix(dev): use CLI tsconfig outside repository

* fix(cli): preserve expanded tool details after completion

* fix(cli): fail headless runs on model API errors

* fix(core): expose shell timeout bounds in code mode

* feat(core): add code mode timeout helpers

* feat(cli): expose code mode in settings

* fix(mcp): preserve structured results for model and exec consumers

* fix(core): preserve code mode output and truncate inline

* feat(core): port scoped context tools to code mode

* fix(build): synchronize pnpm lockfile and capability docs

* fix(core): recover from rejected Responses encrypted reasoning
2026-09-11 07:40:35 +00:00
顾盼
5014a091ab
chore(release): bump node repl to 0.1.3 (#11299) 2026-09-07 09:57:30 +00:00
易良
5b470befa0
ci: make the Prettier lane a real gate (#11117)
* chore: format the 35 files CI had been silently rewriting

`scripts/lint.js --prettier` and the release workflow's `Format Project`
step both run `prettier --write`, which reformats in place and exits 0.
Neither is followed by a dirty-tree check, so the rewrites are discarded
when the job ends and these files have stayed unformatted on `main`.

This is the output of `prettier --write` over exactly those files, so the
next commit can turn the check into a real gate without failing on a
backlog. No behaviour change: every hunk is whitespace, quoting or
wrapping.

Verified with prettier 3.6.1, the version pinned in package-lock.json.
`prettier --check .` over the whole repo reports these 35 and nothing
else.

Refs #11109

* fix(ci): make the Prettier lane a real gate

`runPrettier()` ran `prettier --write .`. Prettier exits 0 after
reformatting, and no caller checks the tree afterwards, so the lane
passed on unformatted code and CI threw the rewrites away — the 35 files
in the previous commit are what it had been silently fixing and
discarding.

ESLint does not cover the gap: eslint.config.js loads
eslint-config-prettier, which disables formatting rules rather than
enforcing them. With this change the only enforcement point outside the
husky pre-commit hook actually enforces something.

`npm run lint:all` now reports unformatted files instead of fixing them;
`npm run format` is the fix. Prettier's own failure message says so.

Refs #11109

* fix(release): drop Format Project, it writes and discards

The step ran `npm run format` (`prettier --write .`) and nothing in
release.yml inspected the tree afterwards — no `git diff --exit-code`, no
`git status --porcelain` anywhere in the file. The rewrites died with the
job, so the only failure it could produce was Prettier crashing on an
unparsable file, which the Run Lint step reports anyway.

It cost 13m58s on a quiet host and 20m45s on a contended one, against a
30-minute cap that `quality_static` hit twice today (runs 33957952281 and
33963757913, both cancelled mid-lint with nothing failing). The remaining
steps are `npm ci` (1m44s) and `lint:ci` (~8m20s), so the job now has real
headroom instead of finishing 24 seconds over the wall.

Formatting is now gated where it belongs: the previous commit makes the
PR-CI Prettier lane `--check`.

Refs #11109

* fix(ci): keep prettier --check out of .qwen/ and the pre-paint theme script

Two things the `--write` → `--check` flip broke that `--write` had hidden.

`.qwen/` is gitignored as `.qwen/*` and holds machine-written state —
pending-skills/, auto-skill-*/, learned-skill-*/,
computer-use/installed.json. Prettier does not read .gitignore, so it was
walking in and silently reformatting whatever a contributor had there;
under `--check` that turns into a failed `npm run lint:all` on files git
does not even track. CI never saw it (fresh checkout, tracked files only),
which is exactly why it went unnoticed. Adding `.qwen/` to
.prettierignore also drops the one tracked file under it —
skills/find-simplifications/SKILL.md, force-added past .gitignore — from
the previous formatting commit, since a directory the gate should not
police should not be reformatted for it either.

The inline theme script in web-shell's index.html is restored to its
original single line behind `<!-- prettier-ignore -->`. It is a
render-blocking script in <head> that applies the stored theme before
first paint; the expanded form ships ~600 more bytes on the critical path
and buys no readability the comment above it does not already give. A
change about CI hygiene should not be reshaping that.

Refs #11109

* fix(ci): keep prettier --check out of vendored packages/mobile-mcp

eslint.config.js has ignored `packages/mobile-mcp/**` since it landed —
"vendored mobile-next/mobile-mcp; has own eslint config" — and
.prettierignore already carries its sibling `packages/cua-driver/`, listed
one line above with the same rationale. mobile-mcp was simply missed.

`--write` hid that: it reformatted the vendored package's own eslint
config to our house style every run and threw the result away. The
formatting commit in this PR would have made that permanent, widening the
diff against upstream on the next sync for a package this repo has already
decided not to lint. That file is reverted here and the directory is
ignored.

Same class as the .qwen/ fix in the previous commit: the gate reaching
somewhere the repo had already decided it should not.

Refs #11109

* fix(ci): mirror eslint's remaining global ignores in .prettierignore

`--check` makes .prettierignore load-bearing in a way `--write` never was:
under `--write` a file prettier should not touch was reformatted and the
result discarded, so nobody noticed; under `--check` it fails the lane.

These three are the rest of eslint.config.js's global ignores, added for
the reasons it already states. `scripts/codemod/fixtures/` is the one that
matters: those files are "intentionally non-idiomatic ink input/output",
so the obvious response to a `--check` failure there — reformat to house
style — would corrupt the fixtures the codemod tests assert against. The
trap is not live today (all three currently satisfy prettier), which is
precisely why it is worth closing before someone meets it.

Refs #11109

* chore: format converter.test.ts, which drifted onto main since this branch

This branch merged main 32 commits after its base and `prettier --check .`
found one new offender. It is the same gate failing the same way: nothing
enforces formatting, so `--write` reformatted this file on every CI run
since it landed and threw the result away.

Worth noting as evidence rather than noise — the backlog this PR fixes was
not a one-off from before someone got careless; it accrues continuously,
and it accrued again while the PR was open.

Refs #11109

* Revert "fix(release): drop Format Project, it writes and discards"

This reverts the release.yml half of this PR. #11195 removes that step on
its own, in one file and four lines, and two PRs racing on the same
deletion helps nobody.

The reasoning and the evidence are unchanged and now live on #11195: the
dry-run release dispatched from this branch with the step removed
(34022919438) went 18/18 green in 65m43s, quality_static finished in
7m00s against main's 8m57s, and Run Lint was 4m52s vs 4m43s — nothing
downstream ever read what the step produced.

Dropping release.yml from this PR also widens who can review it. It was
the only file here owned by @pomelo-nwu and @wenshao alone; what remains
under CODEOWNERS is /packages/core/, whose owner list is six people. The
Prettier gate no longer waits on the two maintainers who must sign off on
release workflow changes.

Refs #11109, #11195

* fix(ci): make the gate resolve the file set npm run format resolves

Two review findings, one root cause: the gate was checking a different set
of files than the tree it is supposed to police.

**The `.qwen/` ignore rested on a false premise (R1-1, @chiga0).** Prettier
3.6's `--ignore-path` already defaults to `[.gitignore, .prettierignore]`,
so every machine-written path that entry's comment named —
`pending-skills/`, `auto-skill-*/`, `learned-skill-*/` — was outside the
gate before it via `.gitignore:32`. What the entry actually excluded is the
corpus `.gitignore` deliberately re-includes: `.qwen/commands/**`,
`skills/**`, `agents/**`, `team-memory/**`, `review-context.json` — 57
tracked, hand-authored files. The gate then passed because the surface had
been narrowed, not because the tree was formatted, and contributors editing
those docs could ship them unformatted with both enforcement points silent.
The entry is dropped and the first commit's formatting of
`skills/find-simplifications/SKILL.md`, reverted alongside it, is re-landed.

**The gate and the remedy used different CLIs (R1-2).** `runPrettier()` ran
the classic `prettier --check .` while `npm run format` runs
`prettier --experimental-cli --write .`. The classic CLI reads only the
repo-root `.gitignore`; the experimental one also honours nested ones, and
this repo has 23 of those. Verified on a scratch tree: given
`pkg/.gitignore` containing `lib/`, classic reports `pkg/lib/nested.js` and
experimental skips it. So the lane could go red on a locally built package's
output that `npm run format` provably cannot clear. The gate now uses the
same CLI as the remedy it names.

Refs #11109

* fix(ci): let the pre-commit hook cover what the gate now checks, and pin it

**The hook was narrower than the gate (R1-5).** `lint-staged` formatted only
`*.{js,jsx,ts,tsx}` and `*.{json,md}`, but the gate checks every extension
Prettier has a parser for. Of the 35 files the first commit formatted, 29
(83%) are in classes the hook never touched — 13 `.yml`, 12 `.mjs`, 3
`.css`, 1 `.html`. Left alone, the backlog this PR just cleared would
re-accumulate, and now as a red CI step on someone else's PR rather than as
silent drift. `.mjs`, `.cjs`, `.yml`, `.yaml`, `.css` and `.html` join the
Prettier-only group; ESLint's globs are unchanged, since the gap is about
formatting coverage, not lint coverage.

**Nothing pinned the flag (R1-4).** `scripts/tests/lint.test.js` exercised
only the linter-directory helpers, and `ci-platform-lanes.test.js` asserts
that the `Run Prettier` step exists and is profile-gated, not what it runs.
A refactor, or a well-meant "make lint auto-fix again", could restore the
silent pass with every suite green. Two assertions now pin it: that
`runPrettier()` checks rather than writes, and that it uses the same
Prettier CLI as `npm run format`, so the two cannot drift apart again the
way R1-2 found them.

Refs #11109

* docs: correct the instructions and comments the --check flip falsified

**Three tracked instructions to automation agents now say the opposite of
what the code does (R1-9), and one is fed to a CI lane today** —
`.github/workflows/qwen-triage.yml` re-pins `verify-pr` from the base commit
and hands it to the verifying agent.

- `verify-pr/SKILL.md` told agents never to run `scripts/lint.js` with no
  arguments because it would rewrite the working tree under their A/B
  harness. That is no longer why. The prohibition is kept — it is still
  right — but re-grounded in what the no-arg form actually does now
  (`setupLinters()` wipes the linter temp dir and re-downloads three pinned
  binaries), and `--prettier` joins the enumerated non-mutating checks it
  was the only flag missing from.
- `find-simplifications/SKILL.md` and its `references/land.md` told the
  simplification agent that a formatting diff "carries zero signal" because
  CI runs `--write`. An agent believing that would skip `npm run format`
  after a deletion changed line shape — on the very extensions the hook does
  not cover — and go red on formatting it was told could not matter.
- `update-ecs-runner-qwen-workflow.test.js`'s comment justified parsing two
  matrix layouts by "CI runs `prettier --write .` before this suite". CI no
  longer rewrites anything; the two layouts are still worth parsing, so the
  comment is re-grounded in the checked-in form versus older branches.

**The index.html rationale overstated its own number (R1-8, @chiga0).** It
claimed the expanded script ships "~600 more bytes"; measured against this
repo's Prettier 3.6.1 and its `tabWidth: 2`, it is +258 source and +8
gzipped here, +287/+20 by the reviewer's method — either way roughly half
what was claimed, and "~600" is what the same expansion costs at
`--tab-width 4`. Since the prose ships verbatim inside the built
`index.html`, the fix is to drop the contested figure and keep the comment
short. The `<!-- prettier-ignore -->` guard itself stays: it is load-bearing.

Refs #11109

* chore: format classify-profile.test.mjs, drifted again after commit 1

Third instance in this PR's lifetime, and the sharpest: commit `b1b94a8762`
formatted this exact file as one of the 35, and `main` reintroduced
unformatted content in it within a day. The other two were
`converter.test.ts` at the first merge and this file at the second.

The gate is the fix; until it lands, every merge from `main` re-opens the
same hole.

Refs #11109
2026-09-07 00:44:01 +00:00
易良
d50b0283ef
fix(ci): keep worker tests responsive under ECS contention (#10813)
* test(ci): stabilize contention-sensitive worker tests

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(node-repl): bound native addon builds

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(node-repl): terminate stalled addon build trees

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(node-repl): spawn native builds in a process group

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-09-02 10:49:35 +00:00
顾盼
2b8f73c1e9
fix(cua): harden Node REPL and Windows UIAccess (#10666) 2026-09-01 03:39:19 +00:00
qwen-code-dev-bot
bc186116c6
fix(ci): align non-Linux Vitest unhandled-error handling across unit suites (#10443)
* fix(ci): extend the vitest RPC-timeout exemption to every unit suite (#10438)

* refactor(ci): collapse duplicated vitest exemption rationale to witness pointer (#10438)

* refactor(ci): extend vitest rationale pointer consolidation to remaining unit configs (#10438)
2026-08-31 08:59:48 +00:00
顾盼
b89ba11ede
fix(cua): harden Computer Use sessions and instructions (#10319) 2026-08-28 14:54:35 +00:00
Shaojin Wen
3a46420d59
fix: repair the Windows test lane's standing failures (#10064)
* fix: repair the Windows test lane's standing failures

Fixes the eight failure clusters that keep Test (windows-latest) red on
the ECS nightly lane — three product bugs and five test bugs.

Product:
- extension-git-client: GIT_CONFIG_GLOBAL was os.devNull, whose win32
  spelling ('\\.\nul') Git for Windows rejects with "Invalid argument",
  failing every restricted-env git command on Windows. Use the literal
  '/dev/null', which git's compat layer maps on every platform.
- sessionArtifacts: the pre-open/post-open identity check compared
  numeric dev/ino. NTFS file ids are 64-bit and lose precision above
  2^53 as Numbers, so two files created close together can round to the
  same ino and defeat the swap (TOCTOU) detection. Compare bigint stats.
- dws-event-stream: clearing a stale stderr error on a healthy stdout
  line was gated on the child still being alive, but on Windows the pipe
  routinely drains only after exitCode is set, so the final healthy line
  never cleared it and close reported the stale error. A clean (code 0)
  exit now certifies recorded errors as stale at close.

Tests:
- daemon-git-worktree-guard: fixtures pasted native Windows paths into
  command strings unquoted; on the bash lane the tokenizer consumes the
  backslashes (C:\t\repo -> C:trepo), so resolvable targets tested as
  unresolvable ones and cd shifts silently failed in place. Interpolate
  through cmdPath(), which spells them with forward slashes on win32 —
  what bash preserves and Windows resolves. Assertions keep the native
  spelling; the guard canonicalizes targets through realpath.
- review/lib/worktree: git marks a linked worktree's .git gitfile hidden
  on Windows and CREATE_ALWAYS on a hidden file fails with EPERM;
  rewrite gitfiles via unlink-then-write.
- config: compare getCwd() against path.resolve(targetDir) — the
  constructor resolves it, so the POSIX fixture comes back
  drive-qualified on win32.
- SessionMessageHandler: complete the pathToFileURL shim's mirror case —
  parse POSIX-spelled fixture paths with { windows: false } on win32 so
  expected file:// URLs read the same on every host.
- node-repl.semantics: dispose the kernel before removing its cwd (a
  process's cwd cannot be removed on Windows) and retry the removal
  across the child's asynchronous teardown.

* fix: witness the Windows-lane fixes per review feedback (#10064)

- Document why the workspace-status identity fstat cannot be folded into
  the Number stat (bigint stats truncate mtimeMs and return BigInt fields).
- Add a clean-exit fixture + test proving the dws consumer reports the
  code-0 process error instead of a stale stderr error.
- Add an injected-stats test detecting swaps whose file ids differ only
  above 2^53, so a numeric identity revert cannot ship green.
- Route the remaining unquoted/eval'd env-var assignment payloads in the
  worktree guard tests through cmdPath so they stay resolvable on the
  win32+bash lane.

* test(acp-bridge): consolidate the duplicated forwarding lstat spy (#10064)

---------

Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-08-26 16:12:36 +00:00
顾盼
f9f5f2fcb6
fix(node-repl): make package verification idempotent (#10133) 2026-08-26 10:06:50 +00:00
顾盼
37cedea5b2
feat(computer-use): replace built-in tools with bundled skill (#9856) 2026-08-24 11:05:23 +00:00
顾盼
be891657f7
refactor(node-repl)!: deliver the persistent Node REPL as a standalone MCP server (#9499)
* feat(core): add persistent Node REPL runtime

* fix(core): align Node REPL phase-one contract

* fix(core): align Node REPL module compatibility

* fix(core): remove unused Node REPL host broker

* refactor(node-repl)!: deliver as a standalone MCP server, revert core tools

Replaces the three built-in `packages/core` node_repl tools with a
self-contained MCP server package, `@qwen-code/node-repl-mcp`.

Why
---
Issue #9333 triage accepted the runtime only "for exploration" and gated it
on an open maintainer decision: built-in core tool vs MCP-server-first.
Reversing OpenAI Codex 0.149.0 settled the shape — it ships code mode as a
standalone host with an in-process fallback, and its real-Node `js_repl`
(not the restricted V8 `exec`) is the analogue this roadmap needs, because
stage 3 (#9335) imports cua-driver's N-API addons, which only real Node can
load. Delivering out-of-core also keeps a security-relevant subsystem out of
the maintainer-gated `packages/core` until its value is proven.

What changed
------------
- New `packages/node-repl`: the kernel, module loader, cell transform,
  protocol and kernel manager, ported and now dependency-free (local
  `debug-log`/`win-path`/`tokenizer` replace core utils; `output-adapter`
  emits MCP content blocks in place of `result-converter`).
- Reverted every `packages/core` change, the ~11 packaging files that existed
  only to ship the runtime assets into six distribution layouts, and the two
  design/plan docs describing the core delivery.
- Deleted the trusted-package/sha256 layer: it was empty and unreachable in
  production (`module-loader.mjs` 882 -> 483 lines).
- Wired the package into `scripts/build.js` and the root `vitest.config.ts`.

Net effect: `packages/core` is untouched relative to main; the tool is opt-in
via `mcpServers` instead of registered unconditionally for every user.

Correctness fixes made while porting
------------------------------------
- Stack line numbers were wrong and drifted with binding count (source line 4
  reported as 87). The prelude is now one physical line and the cell compiles
  with `lineOffset: -1`.
- Top-level `var` nested in blocks/try/switch/loops was silently dropped;
  collection now walks the statement subtree, pruning at function boundaries.
  Verified against real Node for 16 constructs.
- A binding named `nodeRepl` permanently broke the output channel; such cells
  are now rejected. Ordinary globals stay shadowable, matching plain Node.
- Errors thrown by imported modules lost their class, `code` and stack.
- A throwing frame handler could discard buffered protocol frames.
- A hoisted `var` assigned before a throw is now kept, as Node does.
- Unhandled rejections settling after a cell no longer vanish.
- Live sandbox timers are capped, so one runaway loop cannot saturate the
  session's event loop.
- Image MIME types are matched case-insensitively on both input paths.
- Binding sort no longer depends on host locale collation.
- The published bin lacked a shebang and mis-detected its entry point, and the
  server plus its kernel leaked on every host disconnect.

Tests: 146 across 14 files, including a compiled N-API addon fixture, 100
consecutive cells, 10 concurrent isolated kernels, stack-line fidelity, and
hoisting semantics. Three smoke scripts cover the adapter, the MCP wire and
process lifecycle; the packed tarball was installed into a clean project and
driven end to end.

Refs #9333

* fix(node-repl): pin zod to the hoisted 3.x so the workspace build type-checks

`packages/node-repl` declared `zod ^4.1.13`, so `npm ci` installed a nested
zod 4.4.3 for it while `@modelcontextprotocol/sdk` resolved the hoisted
zod 3.25.76. Two zod type identities in one compilation made every
`registerTool` input schema unassignable (`Type 'ZodString' is not assignable
to type 'AnySchema'`), failing `npm run build` for this workspace — and with
it the install step of every CI job that builds workspaces.

The SDK accepts `^3.25 || ^4.0`, so pin the hoisted 3.x line and drop the
now-stale nested lockfile entry. One deduped zod, no behaviour change.

This only reproduced with a lockfile-driven install; the local tree had no
nested copy, which is why the build passed locally and failed in CI.

* fix(lint): lint package .mjs node scripts with the node env

The eslint "scripts we run with node" override matched
`packages/*/scripts/**/*.js` but not `.mjs`, nor a package-root `build.mjs`.
`packages/node-repl` is `type: module`, so its `build.mjs` and `scripts/*.mjs`
were linted as browser code and `eslint .` failed with `'process' is not
defined` / `'console' is not defined` / `'setTimeout' is not defined`.

The pre-commit hook only lints `*.{js,jsx,ts,tsx}`, so `.mjs` files are not
checked locally — this only surfaced in the root CI lint step.

Add `packages/*/scripts/**/*.mjs` and `packages/*/build.mjs` to the override,
matching the existing `.js` entries. Generic, additive, no behaviour change.

* fix(node-repl): address round-3 review findings (resolution, error identity, image bound)

Triaged the bot's round-3 critical findings against the ported code and fixed
the three that were genuine defects here; each has a regression test.

- R3-5 (resolution): a symlinked `<cwd>/node_modules` — the norm under pnpm,
  monorepo hoisting and shared CI caches — was silently dropped, so the
  documented zero-config `await import('pkg')` failed with "cannot resolve from
  0 module roots" while plain Node resolved it. The implicit cwd root was
  applying a re-link self-comparison that only makes sense for registered roots
  (which carry a registration-time canonical baseline). Follow the symlink for
  the implicit root, but skip it entirely when the cwd node_modules is itself a
  registered root, so the registered root's revocation guard is not undermined.

- R3-3 (error identity): an error thrown by a host builtin inside imported code
  (e.g. `fs.readFileSync` → ENOENT) was rewrapped message-only, dropping
  `code`/`errno`/`syscall`/`cause`/`stack` — so `catch (e) { if (e.code ===
  'ENOENT') }`, a ubiquitous Node idiom, silently took the wrong branch. Carry
  those fields onto the realm-wrapped error.

- R3-6 (image bound): image-frame `mimeType` was unbounded — only `data.length`
  counted against the raw image budget — so a malformed/forged frame could
  retain a huge string that also got interpolated into a notice and tokenized.
  Bound the MIME at frame ingestion (a real image MIME is a few dozen chars).

- R3-4 is by design (the runtime is not a security boundary), but the tool
  description recommended `createRequire` without noting it is not subject to
  the process denial or module-root containment the import path enforces; the
  description now says so.

- R3-1 / R3-2 (the `.mjs` lint failure) were already fixed in an earlier commit.

Suite: 148 tests (added symlinked-cwd resolution and host-builtin error-code
regressions). The symlink fix initially defeated the registered-root revocation
test; the registered-root deferral above resolves both.

* docs(node-repl): note top-level function/class re-declaration needs a fresh name

Round-3 review R3-12/R3-33: re-declaring an existing top-level function or class
in a later cell is a link-time SyntaxError (they persist as `let`, which the
prelude re-declares), whereas a plain Node REPL accepts it. A correct fix needs
declaration-kind tracking the manager does not carry today; until then the tool
description's rerun guidance ('prefer var') is corrected, since a function cannot
be made rerunnable via var.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-23 14:20:39 +00:00