Commit Graph
5 Commits
Author SHA1 Message Date
Shaojin Wen 1df5031e70 perf(ci): overlap web-shell and standalone tests with the CLI suite (#15438)
* perf(ci): overlap web-shell and standalone tests with the CLI suite

The Linux Test job ran every workspace suite, the standalone runtime
regression and scripts/tests one after another. The CLI suite keeps its
workers busy for 13-20 minutes, while web-shell's suite spends most of its
run on App.test.tsx alone and the standalone regression starts one
runtime process at a time, so those two added their mostly idle minutes
straight to the job.

Run them in a side lane beside the other workspaces instead.
.github/scripts/ci/run-unit-test-lanes.sh starts web-shell and the
standalone regression in the background, runs every other workspace's
test:ci (listed with npm pkg get, in npm's workspace order) and then
scripts/tests, and replays the side lane's log after the main lane so
the two never interleave. The step runs the script under the same
setpriv wrapper, so every test command still runs without the DAC
override capabilities.

test:scripts now reaches its Vitest run through a new test:scripts:unit
script with a trailing --, so the main lane can run it without the
standalone regression while test:ci, test:ci:workspaces and test:scripts
run what they ran before.

* fix(ci): name the cause when the lane script cannot start a lane

- Fail with an ::error:: line when mktemp cannot create the side lane's
  log file, instead of running the main lane alone and dying in tail.
- Check the exit status of `npm pkg get` and of the parser. A failing
  `npm pkg get --json` prints an error object, which used to parse as an
  empty listing and report "Found no workspaces".
- Note in the header that the replay puts side-lane failures after the
  main lane's in main-failure-signature.mjs's title and search markers.
- Lane test: clear the caller's NODE_OPTIONS, assert the main lane's
  output is present before checking its order, and cover the failed
  listing and the missing temp dir.
- Cross-check npm's workspace resolution against the selector in
  scripts/workspaces.js.

* fix(ci): halve web-shell's worker cap beside the main lane

Each Vitest run applies VITEST_MAX_FORKS / VITEST_MAX_THREADS on its own,
so running two lanes at once doubled the step's worker cap, and on the
hosted runners, where no cap is set, put two all-CPUs pools on 4 vCPU.
The lane script now runs web-shell at half the cap, at least 1, and at 2
where the cap is unset; the main lane keeps the full cap.

- Refuse a workspace name with whitespace in it: the main lane's
  arguments are read one per line, so a line break would split a name
  into other arguments, flags among them.
- Lane test: web-shell and the main lane's suites now wait for each
  other, so a side lane serialized before or after the main lane fails;
  record each call's worker caps; run the script against npm's real
  listing and compare its main lane with the selector the release pins
  use; cover a name with a line break in it.
- ci.yml: the standalone runtime cache comment names the lane script's
  side lane, not test:scripts, as what runs the regression in this job.
- ci-variables.md: say the cap applies per Vitest run and what the
  two-lane step runs at its peak.
- release-workflow.test.js: follow test:scripts through
  test:scripts:unit to vitest; #15102 pinned the shape before the split.
2026-10-25 10:05:56 +00:00
易良andQwen-Coder 73aa65a4b4 chore(desktop): rename packages/desktop-shell to packages/desktop (#12653)
* chore(desktop): rename packages/desktop-shell to packages/desktop

The Electron-era packages/desktop was removed in #9085 and the OpenWork
downstream has forked, so the short name is free and the shell suffix is
the only thing left of the transition. Rename the package directory and
every load-bearing reference: workspace negations (npm + pnpm), the ci.yml
crate filter, desktop-release.yml, the isolation guard, lint/ignore configs,
scripts tests, CLI workspace-fixture tests, and living docs.

User-facing identity is untouched: the crate stays qwen-code-desktop,
productName stays "Qwen Code Desktop", the bundle identifier, updater
feed, release tag scheme, and the legacy electron-bridge asset names are
all unchanged. check-desktop-isolation now guards both path names so the
old one cannot re-enter a workspace set. Dated design docs under
docs/design/ keep their historical paths deliberately.

Refs #8596

* docs(design): follow the rename in the undated notification design notes

The web-shell-browser-notification-details pair is a living doc without a
date prefix, so it falls under the PR's update rule rather than the
historical-record exception.

* docs(desktop): stop citing paths the rename invalidated

Three prose sites and one CI comment still described the pre-rename world,
or contradicted themselves once `packages/desktop` named both the removed
Electron app and the living Tauri shell.

- ci.yml `desktop_shell`: the comment this branch added credited the
  Cargo.toml guard with keeping "branches from either side of the rename"
  green. It does not. A pre-rename head's files sit under
  `packages/desktop-shell`, which the changed-files filter no longer
  matches, so the job skips before the guard ever runs. Say that, and say
  what the guard does cover (a head the filter matched because ci.yml
  itself changed, with no crate at the new path).
- docs/design/web-shell-user-message-links.md: "desktop-shell routing" /
  "desktop-shell clicks" -> "desktop-host", the wording this branch already
  chose in LinkifiedText.tsx for the same rule.
- docs/design/telemetry-runtime-client-attribution-design.md: repoint the
  `runtime.rs` citation. It backs the live `QWEN_CODE_DESKTOP=1` claim, and
  the other document of record for that invariant -- the comment in
  acp-channel-fallback.ts -- was already repointed by this branch, so the
  two disagreed. Only the path token changes; the doc's dated findings are
  untouched.
- brand-create.mjs / SKILL.md / src-tauri/src/main.rs: name the historical
  referent as historical (the Electron `packages/desktop` removed in PR
  9085) instead of letting it read as a self-reference. In main.rs the
  citation `packages/desktop/packages/shared/src/config/storage.ts` now
  looked like a subpath of the file's own package. Rewritten, not deleted:
  the `QWEN_DEFAULT_WORKSPACE_DIR` clause next to it is the only in-code
  documentation of that override and still resolves.

Deliberately not swept: `scripts/check-desktop-isolation.js`'s
`packages/desktop-shell` tripwire, ci.yml's #8132 history comment,
`docs/design/9152-architecture-invariant-classification.md` (names both
spellings and is accurate), and the date-prefixed
`docs/design/2026-07-31-desktop-web-shell-release.md`.

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

* test(scripts): pin the desktop crate path and the isolation guard's coverage

Both invariants fail green today, and this rename is the second time the
crate directory has moved.

`scripts/check-desktop-isolation.js` gained `'packages/desktop'` in
`nativePrefixes` -- the only production-logic line in this branch -- and
nothing read it. Deleting that line leaves the guard's output and exit code
byte-identical, because `isNativeLocation('packages/desktop')` then matches
no remaining prefix; a later PR that drops `!packages/desktop` from the root
workspace list would re-admit the package with CI green.

The `desktop_shell` job names the crate directory in five places (the
changed-files filter, the Cargo.toml guard and its `::notice::` text, the
rust-cache `workspaces:`, and two `working-directory:` values). Nothing
asserted they agree with each other or with a directory that exists, so the
next drift reports a skipped job as a passed one.

The new suite parses both instead of re-running them, and is superset-shaped
on purpose: `nativePrefixes` carries a fourth entry (the
`packages/desktop-shell` tripwire) that nothing negates, and an exact-match
assertion would invite deleting it. The npm/pnpm mirror of the negation list
is already pinned by package-scripts.test.js, so this reads package.json only.

Verified by mutation, each applied alone and then restored: reverting the
filter alternative or either `working-directory` to `packages/desktop-shell`,
dropping `'packages/desktop'` from `nativePrefixes`, and dropping
`!packages/desktop` from package.json all turn this suite red. Today no other
test does.

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

* test(scripts): pin what the desktop-isolation tests actually claim

Four review findings on scripts/tests/desktop-isolation.test.js, each
verified by mutating the thing it claims to pin, before and after.

- The comment above the local `isNativeLocation` copy claimed a change to
  the script's rule "has to be made here too". Nothing reads the rule back
  from the producer -- `nativePrefixesFromSource` parses the array literal
  only -- and narrowing the script's rule to prefix equality leaves the
  suite green. Describe the copy instead of claiming coupling that does not
  exist.
- `still carries the Cargo.toml existence guard` matched only the guard's
  condition text. Flip the body's `changed=false` to `changed=true`, or move
  it after `fi`, and the suite stayed green at 5/5 while the job runs every
  gated step against a crate that is not there. Assert the assignment sits
  inside the guard's own `if ... fi` body.
- Both on-disk checks keyed on the bare directory, so git-ignored residue
  under `packages/desktop-shell` (`node_modules`, which survives a branch
  switch with an empty `git status`) reddened two tests on a tree with no
  defect and blamed the root workspace set. Key on `package.json`, which is
  what `packages/*` requires to make a directory a workspace member; a real
  re-entry still reddens both.
- The `startsWith(filterAlternative)` assertion compared a path built from
  `crateDir` -- `filterAlternative` with one trailing slash stripped -- back
  against `filterAlternative`, so it could not fail for any input. Dropping
  the trailing slash from the changed-files filter in ci.yml left it green.
  Replace it with the property that makes the alternative a path prefix
  rather than a bare string prefix.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-09-25 05:41:25 +00:00
5ec29581a5 ci: give every workspace the shared-pool test timeout, not just five (#10915)
* ci: give every workspace the shared-pool test timeout, not just five

`npm run test:ci --workspaces` runs 22 vitest projects and they all land on the
same shared ECS pool, where an identical suite takes about 5x longer depending
only on which host it draws (#10490). Five of those configs were given a raised
ceiling there, one at a time, as each package's flakiness became somebody's
problem: cli, core, acp-bridge, web-shell and vscode-ide-companion. Three more
carry a generous flat value (node-repl 60s, sdk-typescript E2E_TIMEOUT_MINUTES,
scripts/tests 90s).

The remaining fifteen were still on vitest's 5000 ms default: all eleven
channels packages, audio-capture, chrome-extension, webui and both
external-context integrations. That is 99 test files and roughly 3170 cases
whose only timeout on a contended host was 5s.

The workaround pattern was already visible in the tree — four tests had been
hand-patched past the default individually, each in its own commit:

    integrations/external-context/src/auto-recall.test.ts           12s
    integrations/external-context/src/provider-extension-local.ts   20s, 30s
    packages/channels/base/src/ChannelBase.test.ts                   8s

Every one of those is a case that needed more than 5s, found the hard way. This
raises the ceiling once, at the lane, instead of one test at a time.

Uses the `? 60_000 : undefined` form that acp-bridge and web-shell already use
rather than cli/core's `? 60_000 : 15_000`, so the change is confined to the
pool: off it these configs keep vitest's own default and a genuine hang on a
developer machine still fails in 5s, not 60s. 60_000 is the value the five
already-raised configs settled on.

The parity sweep in scripts/tests/unit-vitest-configs.test.ts now covers all 22
configs plus webui's function-form vite.config.ts, so a new workspace cannot
quietly join the lane on the 5s default. It asserts a floor rather than
equality, which lets scripts/tests (90s) and sdk-typescript (180s) keep their
own numbers, and a companion case pins that the off-pool branch stays
`undefined`. Verified both ways: the sweep reports 60000 for every config under
an `ecs-qwen-*` stub, `undefined` off it, and fails when the ceiling is removed
from any single config.

No test, fixture or production code changes; a test can only get more time
than before, and only on the pool.

Prerequisite for quarantining the seven raw wall-clock budgets that live in
these packages (#10912): channels/base and channels/dingtalk guard quadratic
stalls at 1000 ms whose measured regression cost is ~4.1s, so under the old 5s
ceiling there was no room to relax those bounds for a contended host at all.

* fix(ci): close the shared-pool timeout pin's own coverage holes

The review found four ways the pin could certify a green the lane does
not actually have. Close each one in the pin itself:

- packages/qwen-live defines test:ci and already runs on the pool lane,
  but was absent from the swept map; register it (its 60s ceiling is
  unconditional, so it stays out of the off-pool sample).
- Run every on-pool assertion under two runner names sharing only the
  documented ecs-qwen- prefix, so a gate narrowed past the prefix fails
  instead of passing on the stub's own name.
- Stub QWEN_SCRIPTS_TEST_TIMEOUT_MS and E2E_TIMEOUT_MINUTES alongside
  RUNNER_NAME: scripts/tests and sdk-typescript derive their ceiling
  from those knobs, so the pin must test the configs' logic, not the
  ambient environment. The scripts-suite knob test's unset arm now
  deletes the variable for real (stubEnv undefined) instead of relying
  on the ambient value being absent.
- Extend the off-pool sample to every gated ternary (all fourteen this
  PR gates plus acp-bridge/web-shell, which carried the shape already,
  plus the webui function form), so a flat 60s in any of them fails.

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

* fix(ci): pin the unset-RUNNER_NAME state and correct the pin's comment

Add an unset-state arm to the shared-pool timeout pin: the off-pool test
only stubbed a foreign RUNNER_NAME, so a ternary treating a missing
variable as pool (`?? true`, `=== undefined || startsWith(...)`) passed
every test while developer machines — which run with RUNNER_NAME unset —
silently moved to the 60s ceiling. The new arm deletes the variable via
`vi.stubEnv(..., undefined)` and samples every gated ternary plus the
webui function form. Mutation check: channels/base mutated to `?? true`
fails only the new arm; restored, the suite is green.

Reword the comment above configModules: the pin does not replace the
hand-patched per-test timeouts (auto-recall 12s, provider-extension-local
20s/30s, ChannelBase 8s). A per-test timeout outranks the config-level
field and stays load-bearing off the pool, so the pin guards the
config-level ceiling only.

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

* test(ci): pin the shared-pool guard's own coverage instead of sampling it

Three holes in the guard added for the #10490 flake class, all in the guard
rather than in any config:

cli and core were kept out of the off-pool sample on the stated grounds that
they pin a lower value, but nothing asserted that value — flattening either
ternary to the pool number passed the whole file, and a real hang off CI would
have surfaced at 60s instead of 15s. Both off-pool arms now assert 15s.

Those two arms also carried verbatim copies of the same 16-entry sample, so a
newly gated workspace had to be registered in three places, and missing one
left it with a pool floor but no off-pool assertion. One shared list and one
helper now back both arms.

configModules was hand-maintained with nothing cross-checking it against the
workspace set, while `npm run test:ci --workspaces` runs whatever the root glob
resolves — so a new workspace with a test:ci script could join the lane on
vitest's 5s default with the guard green. The map is now checked against the
repo's own workspace resolver, one-directionally.

* refactor(ci): share the test:ci workspace selector between both suites

The shared-pool timeout cross-check re-implemented the test:ci workspace
selector that release-workflow.test.js already carried as
getTestCiWorkspaces: same getWorkspacePackageJsonPaths root glob, same
scripts?.['test:ci'] filter. Two copies of the selection could drift
and let the two suites pin different workspace sets.

Extract the selection once into scripts/workspaces.js as
getTestCiWorkspacePaths(root) and consume it from both tests; the
release-workflow site keeps only a pair adapter attaching the parsed
package.json its assertions read. Pure extraction with no behavior
change: both selectors resolved the same 23 workspaces before and
after.

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

* refactor(ci): rename selector to getTestCiWorkspacePackageJsonPaths

The selector returns workspace package.json paths, so carry the same
PackageJson qualifier as its sibling getWorkspacePackageJsonPaths
instead of making every caller privately know the entries end in
/package.json. Updates both consumers added in this PR.

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

* test(scripts): pin the test:ci workspace selector directly

Every consumer derives its workspace set from the selector, so a
filter mutation that drops a subset stays invisible to all of them.
Add an exact-set fixture pin next to the getWorkspacePackageJsonPaths
tests, with the with-ci workspace outside packages/ to mirror the
integrations/* layout. Verified red against both an over-inclusion
mutant (filter -> true) and a partial-drop mutant.

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

* test(scripts): make the selector pin and off-pool arms witness-bearing

Addresses the two low-severity coverage findings from the sandboxed
verify report (comment 5548150043), both measured open at this head:

- Finding A: the exact-set pin mirrored only the flat integrations/*
  layout, so a filter mutation dropping the nested packages/channels/*
  subtree stayed invisible to all three selector consumers (mutant S3
  survived 105/105). Mirror the nested layout in the fixture too:
  explicit packages/channels/* entries in the root workspaces list
  (packages/* does not match nested directories), one member with a
  test:ci script and one without. S3 now fails the pin, naming it;
  S1/S2/S4 stay caught.
- Finding B: OFF_POOL_DEFAULT and OFF_POOL_PINNED are hand-maintained
  and nothing cross-checked them, so dropping a name silently removed
  its off-pool assertion and a flattened ternary still passed the
  on-pool floor (mutants G5/G6 survived). Add a partition-
  exhaustiveness pin: every configModules entry must sit in exactly
  one off-pool arm and every arm entry must exist in the map. Both
  mutants now fail it.

No production logic changed; pins and fixtures only.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-09-07 09:02:39 +00:00
易良 ce72ddbe6c feat(desktop): remove packages/desktop after OpenWork fork; keep the Tauri upgrade bridge (#9085)
OpenWork (modelstudioai/openwork) has forked the Electron desktop code and is
self-contained now, so retire the Electron package and its release/sync
machinery from this repo:

- Delete packages/desktop (Electron app, live-host app, bun workspace).
- Retire scripts/desktop-openwork-sync.ts and the desktop-openwork-sync root
  script; the OpenWork sync is no longer needed.
- Retire .github/workflows/live-host.yml, live-host-release.yml and
  sync-live-host-to-oss.yml; live-host releases now live in OpenWork. The
  CLI-side packages/cli/src/serve/live code stays for now (separate cleanup).
- Retire scripts/check-voice-guard-sync.js (cli<->desktop parity only) and
  its CI step.
- Clean up remaining references: root package.json workspaces negation and
  package-lock.json, eslint/prettier/yamllint ignores, architecture docs,
  web-shell skill descriptions, and review-lib workspace fixtures/comments
  (renamed to point at packages/desktop-shell, the remaining negation).

Deliberately kept: the Electron->Tauri upgrade bridge — desktop-release.yml
(incl. the electron_bridge input), create-electron-bridge-manifest.mjs,
sync-desktop-to-oss.yml (mirrors Tauri desktop-shell artifacts only) and
everything under packages/desktop-shell.
2026-08-25 05:26:11 +00:00
Dragon 62e45c567f feat(desktop): Add desktop app package with Qwen ACP SDK integration (#3778) 2026-06-11 21:57:20 +08:00