mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
a32684604a12e39eb98de7181d986e33ddd7b3fe
43
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e6ed740eb1 |
feat(mods): add TypeScript authoring and configuration (PR2a) (#13890)
* feat(mods): add TypeScript authoring and configuration * fix(mods): preserve marketplace dependencies and update notices --------- Co-authored-by: qqqys <[email protected]> |
||
|
|
9763580b84 |
feat(mods): run experimental headless Mod commands (#13843)
* feat(mods): run experimental headless Mod commands * fix(mods): preserve marketplace hooks and packaging fixtures * fix(mods): skip ignored Claude hook paths --------- Co-authored-by: qqqys <[email protected]> |
||
|
|
933dc0a614 |
feat(managed-agent): admit glob in new hosted-workspace /2 profiles (#13166)
* feat(managed-agent): admit glob in new hosted-workspace /2 profiles
Hosted Sessions could not find a file the model was not told about: the
file profile has no search and the Shell profile only falls back to a
full durable dispatch per probe. Add the read-only glob tool behind
hosted-workspace-files/2 and hosted-workspace-shell/2, following the H1
precedent: the profile gates what the model is offered, the worker
admits GlobTool, and the frozen managed-runtime-tools/1 identity is
unchanged, so no Java or Broker change is needed.
The Harness validates the optional path with normalizeWorkspaceRelativePath
before acquisition; the worker pins the search to the Session's installed
context (never the mount-wide include list) and rewrites results to
Workspace-relative paths. An oversized result keeps the longest fitting
line prefix with a narrowing hint instead of the output-omitted path.
Glob is pre-approved under the default and auto-edit approval modes like
read_file.
Resolves #13030
* fix(managed-agent): contain glob pattern, links and error paths
Completes the containment boundary this PR creates by admitting glob into
the hosted-workspace /2 profiles:
- Reject a pattern that is absolute or carries a ".." segment. Glob
resolves ".." against the filesystem and treats an absolute pattern as
absolute, so the pattern was a second, uncontained search root.
Compared by segment so a literal "a/..b/*.ts" stays usable.
- Compare realpaths, not lexical paths, so a symlink inside the Session
context that points outside it is refused like the shell branch already
does via admitsDirectory.
- Rewrite glob's error channel too. A refused or failed glob reaches the
model and the durable record the same way its results do, so it leaks
the Runtime host path unless it is rewritten the same way.
- Dispatch the trimmed pattern that was validated; glob treats an
untrimmed pattern as a literal and the false negative is persisted.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmuq3r0k30o
* fix(managed-agent): anchor glob root relativization and clear prettier debt
relativizeGlobText stripped the Session root with an unanchored
split/join, so a nested directory whose path repeats the root lost its
separators and two real paths fused into one that does not exist
(/s/x/s/y.ts -> "xy.ts"). Strip the root only where it begins a path
token; every privacy-boundary case is byte-identical to before.
Also normalizes the two added hunks prettier --check rejects, which the
lint gate's freshness step was short-circuiting.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmuqegteq18
* fix(serve): contain hosted file reads and glob text rewriting (round 3)
- read_file/write_file/edit now realpath-contain file_path against the
Session directory: the glob admission makes an in-context symlink
enumerable, and without this a files-only Session could read a sibling
Session's content through it (R3-1). The ENOENT fallback keeps a missing
path a tool-level not-found, not a traversal accusation.
- relativizeGlobText stands down at the degenerate root '/' (the prefix is
every path's prefix; rewriting only ate separators, including the echoed
pattern's), and bareRoot gains the same leading boundary tokenPrefix has
so a hit whose tail repeats the root string is no longer truncated
mid-token (R3-2).
- Pin the anchoring invariants at the unit level — a tmpdir-rooted Session
cannot express the repeating-root geometry through the worker route
(R3-3).
Co-authored-by: Qwen-Coder <[email protected]>
* fix(managed-agent): preserve search profiles during continuation
* fix(managed-agent): judge the glob args that get dispatched, not the raw ones
Four round-4 review findings, each pinned by a test that goes red under its
own revert:
R2-3 — the pre-acquisition glob guard validated the raw `pattern` while the
value dispatched next to it was the trimmed one, so a whitespace-masked `..`
or absolute shape cleared the guard whose comment says it exists to refuse it,
and was refused only by the worker after a full durable Runtime round trip
with a message that does not name the argument. Trim first, then validate
exactly what is dispatched. Containment was never breached; the executor
applies the same rule to the dispatched value.
R4-11 — `path: null` is not `undefined`, so it fell into the non-string
refusal and was reported as an absolute-path/traversal attempt. Because
argument validation is batch-wide, that one encoding of an unset optional also
cancelled every valid sibling call in the turn. Treat `null` as the omitted
case alongside the blank string; other non-strings are still refused.
R4-9 — the `file_path` containment was gated only on "not shell", so the glob
this same diff admits inherited a rule for a parameter glob's declaration does
not have. A stray relative `file_path` on a glob call — schema confusion with
the file tools sharing the turn, or a hook that stamps the key on every call —
refused the whole search and accused the model of a traversal it never
attempted, uncorrectably, because glob names no such argument. Glob never
reads `file_path`; its own pattern and output containment is untouched.
R4-6 — the workspace activation envelope was built twice in one test file, so
a change to that request shape had to be made in two places and missing one
left half the suite posting a stale envelope silently. Keep the module-scope
pair as the single definition and alias the local names to it.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmurw1tuo1o
* fix(managed-agent): close the glob-profile containment review round
- R3-1: the file_path containment resolves the deepest existing ancestor,
so `write_file` through an in-context symlink to a sibling Session is
refused instead of creating the file there; a genuinely absent path
keeps the tool's own not-found answer.
- R4-2: the Session-directory boundary now names Sessions, not mount
contents — an escape is refused when the realpath lands in another
INSTALLED Session's directory, while a linked dependency inside the
same mount (node_modules/@acme/ui -> packages/ui) keeps the
pre-containment /1 read-through behavior.
- R1-1: glob containment runs over glob's full collected set
(ToolResult.collectedFilePaths), never the mtime display slice the
count is not computed from; and both gates admit the brace-EXPANDED
alternatives, so `{.,..}/**/*.md` or `{/etc,/zz}/host*` is refused
before the search runs.
- R4-3: a left-to-right saturating-product scan bounds the brace
expansion cost (4096 alternatives) at both gates before minimatch
materializes anything — a ~70-byte pattern can no longer block the
shared worker's event loop for seconds. minimatch is now a declared
cli dependency.
- R4-1: the W1 recovery verifier accepts the hosted /2 profiles (and
/2-shell captureBytes), so one /2 Session no longer aborts recovery of
the whole shared storage; the W1 recovery design doc names them in
both languages.
Co-authored-by: Qwen-Coder <[email protected]>
* fix(managed-agent): contain the glob walk, not just the pattern spelling
The /2 glob gates refused `..` by segment equality, so `[.][.]` and `\.\.`
spellings walked out of the Session and turned the output guard into a
host-filesystem existence oracle (existing outside file refused, missing
one answered "No files found"). The saturating-product budget also bounded
alternatives but not nesting depth or ranges, so a nested pattern overflowed
brace-expansion's recursion and `{1..100000}` stalled the shared worker.
- GlobTool gains an opt-in `containmentRoot`; the managed tool set passes the
Session directory and glob prunes every entry whose lexical path, or whose
parent's realpath, leaves it. Existing and missing outside paths now answer
identically. The ordinary CLI does not set the option.
- `checkHostedGlobPattern` replaces the budget scan at both gates: at most
1024 characters, nesting depth 8 and 64 alternatives, bounded from the
pattern's structure before anything expands it.
- Recovery reads profiles through `isHostedWorkspaceProfile`.
Ported from #13168 (
|
||
|
|
abcf23a3d9 |
feat(memory): bundle Mem0 with the main CLI (#12891)
* feat(memory): bundle Mem0 with the main CLI Co-authored-by: Qwen-Coder <[email protected]> * feat(memory): support settings-defined Mem0 credentials Co-authored-by: Qwen-Coder <[email protected]> * fix(memory): preserve bundled Mem0 trust and configuration boundaries Ignore repository-scoped MCP conflicts, require the genuine bundled runtime for confirmation hooks, and use native artifact paths in packaging assertions. Improve field diagnostics and document existing precedence, scope, and verification limits. Co-authored-by: Qwen-Coder <[email protected]> * fix(mem0): close three review Criticals on the bundled Mem0 binding R1-1 (scripts/tests/package-assets.test.js): the two new multi-segment `missingArtifact` entries are compared against a message built with `path.join`, so `mem0/main.js` never matches the `\`-joined Windows path. Normalise the separator in the assertion; single-segment entries and POSIX behaviour are unchanged. R1-2 (packages/cli/src/config/config.ts): the mem0/external-context conflict guard read the merged all-scopes map, so a `external-context` entry that a trusted repository contributes through its own `.qwen/settings.json` (`scope: 'workspace'`) aborted `loadCliConfig` for every operator who configured `memory.mem0` — before `assembleMcpServers` applied the approval gate that would have held that server pending anyway. Limit the settings leg to operator scopes via `isGatedMcpScope`; a gated-scope entry is now overridden by the built-in binding (topTierMcpServers spreads last), exactly as a `.mcp.json` entry of the same name always has been, matching the design doc this PR adds. R1-28 (packages/cli/src/config/mem0-settings.ts): `bundledMem0Hooks` identified the bundled server by duck-typing an env-var name plus `includeTools`, so a repository-authored `external-context` entry reaching the `/hooks` reload path through `getMcpServers()` was filed under `systemHooks` — the one hook source folder trust does not gate — rendered as `[System]` configuration and pointed at a different script than the one the MCP approval dialog showed. Require identity instead: every file-sourced entry carries a provenance `scope`, the bundled one never does. Tests: workspace-scoped override + operator-scoped conflict still throws (config.test.ts), three scoped-entry rejection rows + an unscoped positive control (mem0-settings.test.ts), and a control/negative pair on the real reload path (hooksCommand.test.ts). Each fix was mutation-proved red. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmultfgjtdh * test(mem0): pin the spawn interpreter, timeout slack and both bound copies R1-23: nothing in the repo observed `server.command` or `server.timeout`, and the `memory.mem0.timeoutMs` bounds are declared independently in mem0-settings.ts (zod, the runtime contract) and settingsSchema.ts (the settings-dialog copy). Replacing `command: process.execPath` with `'node'`, or dropping the `+ 5000` slack, left every suite green — a packaged or GUI-launched install routinely has no `node` on PATH, and the same value gates `bundledMem0Hooks` and is interpolated as the hook interpreter. Adds, in mem0-settings.test.ts: `command === process.execPath`, `config.timeoutMs === 5000` / `server.timeout === 10_000` for the default, an accept row at the documented ceiling asserting `35_000`, and reject rows for `0` / `30001` / `1.5`. Adds, in settingsSchema.test.ts, the bound test mirroring the existing `tools.webSearch.timeoutMs` precedent so the JSON-schema copy is pinned to the same documented numbers (docs/users/features/mem0.md: "defaults to 5000, between 1 and 30000"). Mutation-proved individually: `process.execPath` -> `'node'` red, `+ 5000` removed red, `maximum: 30000` -> `60000` red; intact tree green (27 + 61). Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmultfgjtdh * fix(mem0): restore PolarDB direct-import text on retrieval Co-authored-by: Qwen-Coder <[email protected]> * test(mem0): use the real bundled binding in hook reload control Co-authored-by: Qwen-Coder <[email protected]> * fix(memory): Preserve Mem0 operator and legacy preset semantics Co-authored-by: Qwen-Coder <[email protected]> * test(memory): Pin Windows Mem0 package artifact errors Co-authored-by: Qwen-Coder <[email protected]> * fix(memory): Retain Mem0 safeguards across reloads --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
1a5a74bc35 |
feat(core): Add Landlock execution fallback (#12278)
* feat(core): Add Landlock execution fallback Co-authored-by: Qwen-Coder <[email protected]> * fix(ci): Track Landlock helper workflow size Co-authored-by: Qwen-Coder <[email protected]> * docs(sandbox): Clarify Landlock compatibility limits Co-authored-by: Qwen-Coder <[email protected]> * fix(sandbox): Repair installed Landlock helpers and prompt guidance Restore missing execute bits only for the bundled helper, anchor asset lookup to the actual source layout, and describe the effective Landlock boundary in the model prompt. Keep portable asset checks on Windows. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
790bd83c2b |
perf(cli): halve fresh-startup time to typeable and cut RSS by 60% (#12622)
* perf(cli): halve fresh-startup time to typeable and cut RSS by 60% Measured with a clean-environment benchmark following the approach in https://claude.dev/blog/how-we-made-claude-ai-faster/ (interleaved A/B, p75 wall-clock backed by deterministic proxies). Against main, time to a typeable prompt goes from 2.55 s to 1.18 s, `qwen -p` time to first request from 2.15 s to 0.94 s, and RSS at the prompt from 574 MB to 231 MB, with no screen change after the prompt becomes usable. - Skip the supervisor relaunch when it adds no Node flags; restarts re-exec in place and updates run in-process (POSIX, plain TUI and -p). - The bin launcher runs the CLI in-process on POSIX. - Enable Node's compile cache on the default route. - Move startup side effects off the critical path: editor probing, the IDE process walk outside VS Code, and the npm-based update check. - Resolve the git branch before the first frame so the footer does not reflow the screen after the prompt is usable. - Bundle with minifyWhitespace; load the review command and non-common highlight grammars only when needed. Experiments, tradeoffs and open decisions are in docs/design/2026-09-24-fresh-startup-performance.md. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01N1XQv6wm9YJ5rSM53eMSRT * fix(cli): keep global.gc in relaunched sessions On POSIX the bin launcher now runs the CLI in-process and exposes gc at runtime, so --expose-gc is no longer in process.execArgv. The supervised relaunch built its child's flags from execArgv alone, so the session process lost global.gc in --acp, stream-json, --json-fd, file input, advanced.autoConfigureMemory, and on Node without process.execve. Both relaunch paths now take their flags from one helper that adds --expose-gc when gc is exposed but not on the command line. * fix(cli): relaunch when env files injected values #12602 re-boots a one-shot run whenever .env or settings.env injected values, because modules that read the environment at load time (and Node itself, e.g. NODE_EXTRA_CA_CERTS) only see them in a fresh image. The in-process path ran ahead of that check and skipped it for both interactive and -p. Take the in-process path only when no env-file value was injected; otherwise relaunch as main does. * fix(cli): keep the spawned CLI under Bun The Bun standalone flavor (the OpenTUI preview archives) runs the same bin entry. Bun has no v8.setFlagsFromString, so the in-process path crashed every non-fast-path command at startup on macOS and Linux. Under Bun the entry spawns the CLI with --expose-gc, as on Windows. Also correct the entry's header comment, the relaunch-flags helper's doc comment and a displaced test comment, and narrow the design doc's gc claim to the modes that were checked. * fix(cli): install a deferred update after a clean exit Without a supervising parent, the startup check fell back to "Run /update to install the update." for standalone and update-command installs. Record the request in-process instead: once the session ends cleanly, install the update and exit with its code, so the launcher's exit hook reopens the new version as the parent did on main. A second quit during the install waits instead of cutting it short. * docs(design): record the fresh-startup decisions Resolve the open decisions, and describe the env-file relaunch rule, the Bun launcher path, the restored update-on-exit and the independent reproductions, in both language versions. * docs(design): record decision 7 as a manual harness without a CI gate The follow-up (#12674) checks in the benchmark harness for manual runs. The interactive startup counts stop at the typeable prompt, and timer-driven work before it shifts with runner speed, so a CI gate would be flaky. --------- Co-authored-by: Claude <[email protected]> Co-authored-by: Shaojin Wen <[email protected]> |
||
|
|
010941d202 |
fix(standalone): pin @lydell/node-pty-linux-arm64 and fail release on missing prebuilds (#12649)
* fix(standalone): pin @lydell/node-pty-linux-arm64 and make missing prebuilds fatal at release The wrapper publishes a linux-arm64 prebuild at the same 1.2.0-beta.10 pin, but the root and core manifests never declared it, so the linux-arm64 standalone archive and the linux-arm64 Desktop runtime both degraded to "PTY not available" while every other target shipped a working web terminal. With the pin in place every shipped target resolves a prebuild package, so the release archive build can stop degrading silently: release.yml now exports QWEN_STANDALONE_REQUIRE_NODE_PTY_PREBUILD=1 next to the existing audio-capture gate, turning a missing prebuild into a hard failure instead of another #11872. Fixes #11872 * fix(standalone): update pnpm lockfile for the linux-arm64 node-pty pin The two package.json manifests gained @lydell/node-pty-linux-arm64 but pnpm-lock.yaml was not regenerated, so every CI job failed at the frozen-lockfile install with ERR_PNPM_OUTDATED_LOCKFILE. * docs(standalone): align the linux-arm64 PTY guide and tripwire with the pin Pinning @lydell/node-pty-linux-arm64 makes linux-arm64 a supported PTY target, but INSTALLATION_GUIDE.md still told users that archive ships without PTY and reports "PTY not available". CI could not catch the drift because install-script.test.js pinned that exact sentence as its tripwire, so the guide and the assertion have to move together. List all five shipped targets in the guide, retarget the tripwire at the new sentence, and refresh the matching stale comment in desktop-shell's test-release.js degrade fixture - that arm now only fires for a target the map does not know. Co-authored-by: Qwen-Coder <[email protected]> * fix(standalone): complete the node-pty linux-arm64 pin in esbuild externals `create-standalone-package.js` documents that "esbuild.config.js keeps every node-pty specifier external" so the bundled CLI resolves the platform prebuild from `lib/node_modules` at runtime. The external list carried five of the six pinned platform packages and omitted `@lydell/node-pty-linux-arm64` — the exact platform this PR pins. Nothing imports that specifier statically today, so the bundle is unaffected; the gap is that the invariant the archive's PTY support rests on was one entry short of complete. Probe (esbuild, `--platform=node --format=esm`, entry importing both specifiers): - without the entry: `Could not resolve "@lydell/node-pty-linux-arm64"` - with the entry: builds clean and emits the import left external, matching the other five platforms Also corrects two comments this PR rewrote in the previous round. Both claimed the desktop degrade arm "fires only for a target the map does not know (a future platform)". That trigger is unreachable: `desktopTarget()` throws `Unsupported desktop target` for anything outside its five-entry allowlist and `NODE_PTY_PREBUILD_PACKAGE` maps exactly those five, so `prebuildPackage` is always a real name. Verified by running the script: $ QWEN_DESKTOP_SKIP_BUILD=1 QWEN_DESKTOP_TARGET=freebsd-x64 \ node packages/desktop-shell/scripts/prepare-runtime.js Error: Unsupported desktop target: freebsd-x64 at desktopTarget (prepare-runtime.js:351:11) The reachable trigger is the one the warn string already names and the one `test-release.js` reproduces four lines below its own comment: a known target whose prebuild package is unpinned in the source root manifest. Comment-only apart from the single external entry. prettier --check and eslint --max-warnings 0 clean on all three files; node --check passes. Co-authored-by: Qwen-Coder <[email protected]> * test(standalone): pin the node-pty release gate and its target map Closes out three review findings on the node-pty pin. Test- and comment-only apart from one new export; no production behavior changes. R1-1 prepare-runtime.js: the nodePtyPackageSpecs() docstring still claimed its null return "is how an unsupported target degrades". desktopTarget() throws for anything outside its five-entry allowlist, and test-release.js:testRuntimeNodePtyTargetMapping() asserts NODE_PTY_PREBUILD_PACKAGE's keys equal exactly that allowlist, so an unknown target can never reach it. A null here is a dropped pin. Verified by re-running that tripwire's extraction against the edited file: both literals still match and keys == allowlist == the same five targets. R1-2 create-standalone-package.js: export TARGET_PREBUILD_DIR and assert every release target maps to a package the manifests actually pin. The guide sentence this PR adds states that invariant while release.yml:707 makes violating it abort the whole archive step, yet the map had zero test references. The expectation is derived from readNodePtyPackageSpecs() so the version list stays single-sourced, and is one-directional because @lydell/node-pty-win32-arm64 is pinned while no target maps to it. R1-3 install-script.test.js: the release.yml assertion matched only the variable name, which also appears in the packager's usage text, so nothing connected producer to consumer and no test executed the fail() branch at create-standalone-package.js:578. Tightened to the full wiring line and added the behavioral twin of the audio-capture pair: gate on with the addon removed must throw, gate off must still build a PTY-less archive. packageFakeStandalone() gained an env option for this; spreading process.env leaves its 15 existing callers unchanged. Mutation-checked, each mutant applied alone then restored and md5 verified: win-x64 mapped to an unpinned name, gate reader renamed to '2', gate polarity flipped to !==, and release.yml no longer evaluating to '1' are each caught by a new or tightened assertion. The polarity flip was previously undetectable on the PR lane, where the linux-x64 prebuild is present so the gate is never reached. Focused: install-script.test.js 130 passed / 11 skipped (141). prettier --check and eslint --max-warnings 0 clean on all three files. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmufv3et42h * docs(standalone): state the real node-pty degrade conditions in comments prepare-runtime.js said the degrade arm fires only "where the repo pins nothing", but nodePtyPackageSpecs returns null at the first unpinned name of the pair, so it also fires when the wrapper pin is the one that went missing while the warning names the prebuild package. Say "either name in the pair" and note which package the warning names. create-standalone-package.js justified the non-fatal default with "a developer archive stays usable on an unmapped future target", which cannot happen: main() calls copyNativeAddon first and it reads the same TARGET_PREBUILD_DIR in an unconditional path.join(), so an unmapped target dies with a TypeError in the audio step before copyNodePtyAddon runs. Replace the rationale with the case that does reach the warn arm (a target whose prebuild the host install does not have). Comment-only; no behaviour change. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmug1iw422v * test(standalone): make the node-pty gate assertions discriminate Pin the `Required ` prefix on the fatal-gate regex. copyNodePtyAddon builds one message and emits it on both paths, so the prefix-less regex was also satisfied by the degrade warning folded into the child's stderr — the suite would have reported the gate as covered after the prefix was dropped from fail(). The sibling clipboard gate assertion already spells the prefix. Assert QWEN_STANDALONE_REQUIRE_NODE_PTY_PREBUILD structurally on the `Build Standalone Archives` step env instead of as a whole-file substring, so commenting the line out or moving it into a neighbouring step's env goes red. build-standalone-release.js spawns the packager with no env override, so that step's env is the only route to the gate. The comment defending the substring pin named a collision with create-standalone-package.js's usage text, which cannot occur: the searched string is release.yml alone. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmug1iw422v * test(standalone): cover the node-pty target map and the win-x64 dir name Two coverage gaps left by the previous round of gate tests: - The map test looped over TARGET_PREBUILD_DIR itself, so a release target with no map entry was never visited, deleting a whole entry merely removed an iteration, and an emptied map passed vacuously. Drive the loop from TARGETS and assert the lookup before the pin. Still one-directional: @lydell/node-pty-win32-arm64 is pinned while no target maps to it. - Both gate tests reached the packager through packageFakeStandalone, which hardcoded --target linux-x64, the one dialect where the prebuild dir name equals the target name. Add a win-x64 arm so the 'win-x64' -> 'win32-x64' translation in copyNodePtyAddon is covered on every lane rather than only on test_windows. Parameterise the staged-modules fixture and the packaging helper to make that reachable without a Windows host. Test-only change; no production code is modified. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmug5t7mp33 --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
3486cd8f86 |
feat(core): Add the bwrap execution foundation (#12067)
* feat(core): add bwrap execution foundation Extract the internal structured launch, confined worker and trusted completion boundary from the tool sandbox work. Keep runtime policy and CLI activation for subsequent changes, and include worker packaging and standalone Linux acceptance. Co-authored-by: Qwen-Coder <[email protected]> * fix(core): bound streamStdout post-exit settle and harden sandbox plumbing Address PR #12067 review findings: - settle streaming pipe executions on 'close' or a bounded 1s post-exit drain, whichever comes first: settling on 'close' alone wedged the result promise whenever an exited shell left a grandchild holding the pipes (background shells, ACP channel) — @chiga0 F1, ci-bot R1-2 - keep transport-level stdin EPIPE out of the spawn-error slot unless the process left no exit information, so a receipt-confirmed run finalizes confirmed and an interrupted run cleans up (ci-bot R1-26); re-key sandbox evidence retention on the finalized status (R1-24) and prefer the specific transport/spawn error over the generic status error (R1-22) - allowlist the two new sandbox dist assets for standalone packaging and require them at archive build time, with a drift test covering every npm files entry (ci-bot R1-1) - default the payload TERM under --clearenv (R1-4), gate the streaming decoder flush on text mode (R1-7), log post-promote settle chain failures (R1-25), and pin the caller-signal/pipe-transport contract (R1-15), the caller-owned stdin snapshot (R1-31) and both settle-timing directions (R1-30) in tests * fix(core): scope sandbox evidence retention to post-exec uncertainty Address @wenshao's blocking verification finding on PR #12067: keying retention on status.state === 'unconfirmed' retained the control/scratch dir pair for failures that never reached exec — a missing bubblewrap or payload binary — leaking one pair per invocation and breaking the PR's own verifier (33/34 on tip vs 34/34 on the reviewed candidate). - parseBwrapStatus now reports payloadExitObserved when the status wire carries a well-formed bwrap exit-code record (bwrap only emits it once the payload is past exec), and the relay attests payloadExitObserved: false on spawn/transport failure - the finalizer retains only when the payload may genuinely have run: the receipt attests an exit record, or the receipt file exists but is unreadable (relay died mid-payload); a missing receipt means the relay died before the spawn call, and an attested no-exec receipt means setup failed before exec — both clean up - sandboxStatusError distinguishes the attested no-exec case with a "did not run (setup failed before execution)" message (N4) - design docs record the retention scoping and the drain-window abort nuance (N2); PR body now states the bounded-drain trade-off (N1) Verified: core 204/204, tsc/eslint clean, and the relay probed against real processes — missing bwrap and exec failure now produce no-exec receipts (cleanup), an uncorrelated exit record retains, and the confirmed path is byte-identical. * fix(core): keep unknown sandbox receipts from attesting non-execution Address @wenshao's round-2 findings on PR #12067: - the no-exec attestation was being manufactured from absence of evidence at two sites: the finalizer coerced a receipt without payloadExitObserved into an explicit false, and the relay attested false for every `failed` cause although only a bwrap spawn error proves the payload never ran (a status-stream transport error or a 16KiB wire overflow can happen after the payload executed). Both now keep undefined as unknown: only a genuine spawn error or a wire with no bwrap exit-code record attests no-exec. - retention follows: clean up only on a positive no-exec attestation or a missing receipt file (the relay creates it O_EXCL before spawning bwrap, so absence proves pre-spawn death); unknown retains. The fail-safe direction for a retry-deciding caller is restored — an oversized or empty wire with the payload having run now retains and reads "may have run" instead of "did not run". - the no-exec message no longer points only at bubblewrap; it also covers a missing payload binary. - verify.mjs: the mount-setup-failure receipt assertion now expects the payloadExitObserved attestation (the one shipped deepEqual on the receipt object). - bwrap-status tests now discriminate all three attestation states strictly (absent vs false vs true) instead of applying the same coercion the code did. Verified: core 204/204, tsc/eslint clean, relay probed against real processes for six receipt shapes — spawn failure and child-pid-only wire attest false, mismatched exit record attests true, empty and oversized wires stay unattested, success control confirmed. * fix(core): harden bwrap execution foundation Address the remaining execution-foundation review findings with deterministic file-worker framing, separate worker diagnostics, scratch-root admission, CI unit coverage, and stronger Linux acceptance evidence. Co-authored-by: Qwen-Coder <[email protected]> * test(core): Make bwrap verifier portable Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
907667bbd6 |
feat(core): add container execution for subagents (#11711)
* feat(core): add container execution for subagents * fix: address container execution review and CI failures * test(cli): account for relaunch environment provenance * test: include execution worker in package asset fixtures * fix(core): harden container execution boundaries and lifecycle * fix(core): isolate container cancellation and bound cleanup * fix(core): protect container roots and cache recovery * fix(core): report container startup failures and waits * fix(core): reject unsupported teams and arena before setup * fix(core): keep root git metadata masked across worker startup * fix(core): reclaim owned git mount points after worker cleanup * fix: preserve container policy and retry failed cleanup * ci: refresh PR head and rerun checks * fix(core): retain cleanup ownership after container startup failure * fix: fail closed on container policy refusals * fix(agent): close container execution review gaps |
||
|
|
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 |
||
|
|
a60cbbc54a |
refactor(core): make utils/ a leaf layer (#9778)
* refactor(core): make utils/ a leaf layer Eliminate every runtime (value) upward import from packages/core/src/utils production modules so utils/ can become a leaf layer with no runtime dependency on the rest of core. Two mechanisms, no behavior change: - Relocate domain-coupled modules out of utils/ into their owning module (agents, config, core, memory, services, tools), and move generic constants/types that live elsewhere into utils/. All `git mv` moves keep history; every import that pointed at a moved file is rewritten. - Extract the remaining value imports as small leaf modules inside utils/ (AuthType, isTool, ToolErrorType, DEFAULT_QWEN_MODEL) and re-export them from their original owners so cross-package consumers are unaffected. doesToolInvocationMatch moves into shell-utils, its only production consumer. Only type-only imports now cross the utils/ boundary. The two deferred inversions in debugLogger (Storage, getTraceContext) are stateful and left for a follow-up. * chore(core): enforce utils/ leaf layer with lint rule Add architecture/no-core-utils-upward-import, which flags runtime (value) imports that leave packages/core/src/utils. Type-only imports, sibling utils imports, and external package specifiers stay allowed; the two deferred debugLogger inversions (config/storage, telemetry/trace-context) are carried on an explicit allowlist. Enable the rule as an error on core sources and cover it with Linter-based tests. * fix(core): restore iconv-lite tree-shaking for sync-file-encoding The utils leaf-layer refactor moved sync-file-encoding from utils/ to services/, but the esbuild tree-shake plugin still matched the old ./utils/ specifier, so its sideEffects:false marker no longer applied and the ACP startup closure regained a static iconv-lite import. Point the onResolve filter at the new ./services/ path. * fix(ci): catch stale integration imports earlier * fix(core): close utils boundary review gaps * fix(core): close self-reference boundary gaps * ci: re-trigger after self-hosted runner checkout EACCES |
||
|
|
e40263ee55 |
chore(deps): Clear high-severity CVE baseline and harden the security gate (#9584)
* chore(deps): Clear high-severity CVE baseline and harden the security gate - Bump OpenTelemetry stack to 0.221.x (fixes @opentelemetry/core advisories) - Bump @larksuiteoapi/node-sdk to ^1.73.0 and override axios to ^1.19.0 - Bump mobilewright to ^0.0.53 (drops vulnerable sharp 0.34.x) - Bump markdown-it to ^15.0.0 (drops vulnerable linkify-it 5.x) - Update undici/fast-uri/brace-expansion/ip-address within range - Adapt telemetry code to OTel API changes (forceFlush, processor options) - Make security-checks a hard gate now that the high baseline is clean * chore(deps): Refresh mobile-mcp vendored lockfile to drop vulnerable sharp * fix(telemetry): stub sdk-node 0.221 env auto-config helper packages sdk-node 0.221 extracted its env-based auto-configuration into @opentelemetry/configuration, otlp-exporter-base, and otlp-grpc-exporter-base, which it now requires eagerly. The existing esbuild stub only covered the exporter-* packages, so the OTLP protocol chain (grpc-js, protobufjs, otlp-transformer) re-entered the sdk-impl static closure and tripped the serve fast-path bundle guard. Stub the three helper packages when imported by sdk-node only; our own protocol modules keep resolving the real packages. qwen-code never reaches these helpers at runtime (explicit exporters + env scrub). * fix(telemetry): disable metrics fallback without reader * fix(vscode): restore nested dependency notices * fix(deps): declare bundled punycode so its notice survives regeneration The CLI esbuild config aliases punycode to the userland package (esbuild.config.js), so the shipped CLI bundle contains MIT-licensed [email protected]. Its NOTICES.txt section was lost because the only lockfile paths reaching punycode were dev-only; the notice walker (rooted at vscode-ide-companion) never sees a production declaration. Declare punycode as a direct production dependency of the CLI (the bundle input) and of vscode-ide-companion (which packages the bundled CLI into the VSIX and owns NOTICES.txt), then regenerate the lockfile and notices so the MIT notice is restored. |
||
|
|
bd2c0b47f1 |
feat(core): add full-resolution image zoom tool (#7809)
* feat(core): add full-resolution image zoom tool * chore(vscode): refresh third-party notices * fix(ui): satisfy zoom image display drift checks * fix(web-shell): translate zoom image tool * fix(core): wire sharp into packaging and load it lazily (#7809) Externalize sharp in esbuild and declare it (plus the @img platform binaries) in the published package's optionalDependencies so an npm-installed CLI resolves the native binding. Import sharp dynamically inside execute() so a missing binding returns a bounded tool error instead of crashing startup during strict tool warmup, and so the module is only loaded when zoom_image actually runs. Also pin the EXIF auto-orientation test with a discriminating fixture and cover the .qwenignore and y1>=y2 validation paths. * fix(core): gate zoom_image at execute time and cap tiny-crop upscale (#7809) Register zoom_image unconditionally and move the image-modality check to execute time so first-run sessions and hot /model switches resolve the tool without re-running initialize(). Cap magnification at 8x so a tiny crop no longer inflates to the full image-token budget. Drop the redundant @img/* platform pins; sharp's own optionalDependencies install the matching binary for each OS/arch. * fix(core): emit zoom_image file telemetry and cover guard branches (#7809) Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: qwen-code-autofix <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
9bdc62c74b |
perf(cli): replace comment-json settings parser (#7747)
Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
8fa8085036 |
perf(core): Lazy-load first-use dependencies (#7686)
* perf(core): Lazy-load first-use dependencies Co-authored-by: Qwen-Coder <[email protected]> * test(core): Fix simple-git loader mock Co-authored-by: Qwen-Coder <[email protected]> * test(core): Cover abort during xterm load Co-authored-by: Qwen-Coder <[email protected]> * fix(core): Address lazy-loader review feedback Co-authored-by: Qwen-Coder <[email protected]> * fix(core): Validate lazy dependency module shapes Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
636971efad |
perf(telemetry): lazy-load the SDK and split OTLP exporter chains by protocol (#7276)
* perf(telemetry): lazy-load the SDK and split OTLP exporter chains by protocol * fix(telemetry): close lazy SDK init/shutdown races and make load failure non-fatal Addresses PR #7276 review feedback: shutdown now awaits an in-flight init before tearing down (was racing past the sync flag and leaking a started SDK whose buffered spans/logs never flushed); the dynamic imports now sit inside init's try so a chunk-load failure degrades telemetry instead of aborting daemon runtime startup. Also breaks the sdk<->sdk-impl import cycle via a leaf otlp-urls module, hardens the sdk-node exporter stub for thenable/interop probes with a unit-tested separator-independent resolve, lists the HTTP exporter packages explicitly in the bundle guard, and adds lazy-init lifecycle tests. |
||
|
|
2cb0031160 |
fix(cli): add bootstrap fast paths (#6188)
* fix(cli): add bootstrap fast paths * fix(cli): address bootstrap review feedback * test(core): make MCP retry backoff test deterministic * fix(cli): address bootstrap validation feedback * fix(cli): keep global-flag MCP invocations on full parser * fix(cli): harden bootstrap review gaps * fix(cli): copy package wrapper from script directory * fix(cli): cover bootstrap review edge cases * test(cli): cover bootstrap fallback paths * fix(cli): minimize wrapper version imports --------- Co-authored-by: 易良 <[email protected]> |
||
|
|
b9c5e3566b |
feat(voice): voice dictation with native capture, streaming, and biasing (#5502)
* feat(voice): voice dictation with native capture, streaming, and biasing
Add voice dictation for the prompt input:
- /voice [hold|tap|off|status] command + general.voice.{enabled,mode,language,protocol} settings; push-to-talk via Space, /model --voice to pick the model
- Native microphone capture (@qwen-code/audio-capture, miniaudio N-API) with arecord/SoX fallback, silence auto-stop, cold-start warm-up, and macOS permission query
- Batch transcription via DashScope Qwen-ASR (OpenAI-compatible chat/completions + input_audio) with language + keyterm biasing and an echo guard
- Live streaming over the DashScope realtime WebSocket (fun-asr-realtime / paraformer-realtime-v2) with interim text and an input-level waveform, behind voice.protocol=dashscope-realtime
- Rich VoiceIndicator UI (state, level meter, live partial transcript)
- Cross-platform prebuilds via prebuildify + node-gyp-build and a CI matrix
Co-authored-by: Qwen-Coder <[email protected]>
* feat(cli): route voice ASR by model
* feat(cli): polish voice realtime parity
* ci: fix voice workflow checks
* fix(cli): address voice review blockers
* fix(cli): harden voice transcription failures
* fix(cli): address voice PR review blockers
* fix(cli): harden voice review follow-ups
* fix(voice): handle realtime review blockers
* fix(cli): add voice command i18n keys
* fix(cli): address voice review blockers
* fix(cli): address voice review follow-ups
* fix(voice): harden review edge cases
* fix(voice): address release and realtime review blockers
* fix(voice): address realtime suggestion followups
* fix(voice): handle realtime review followups
* fix(voice): address realtime review blockers
* fix(voice): address review feedback
* fix(voice): address recorder review feedback
* docs(voice): document ssrf guard boundary
* fix(voice): address review feedback
* fix(voice): preserve warm recorder session safety
* fix(voice): address stream review suggestions
* fix(voice): address multi-round review findings
Realtime/streaming:
- Salvage an already-committed transcript when the WebSocket closes right
after finish() instead of rejecting the whole dictation
(qwenAsrRealtimeSession, voiceStreamSession) + regression tests.
useVoiceInput state machine:
- Single-shot finalize guard so a tap-stop racing the silence auto-stop can't
double-stop the recorder and surface a spurious failure.
- Reset mountedRef on (re)mount so StrictMode (DEBUG) can't freeze the voice UI.
- Widen the hold-mode first-press release window above common key-repeat delays.
Model selection:
- Reject ids with no ASR transport at /model --voice and in the model dialog via
a new isSelectableVoiceModel; move resolveVoiceTransport into voiceModel so the
record-time config resolver stays transport-agnostic (+ tests).
macOS mic permission:
- Surface the not-determined state in voice warmup so the first dictation isn't
silently lost behind the TCC dialog.
Native packaging:
- Make the audio-capture native install non-fatal (falls back to SoX/arecord) so
a voice-only build failure can't break npm ci.
- Download audio-capture prebuilds before building standalone release archives.
Co-authored-by: Qwen-Coder <[email protected]>
* fix(voice): address review blockers
* fix(voice): update batch recording audio level
* fix(voice): tap-mode transcript loss, stream leaks, and dead keyterm cleanup
- Tap-mode dictation submitted a stale empty buffer.text, wiping the just-
inserted transcript and sending nothing: thread the resulting prompt text
through onSubmit(text) instead of reading buffer.text back synchronously
after the async insert (useVoiceInput, InputPrompt).
- Streaming finalize leaked the WebSocket session when recorder.drain() threw:
abort the session before propagating the error.
- voiceStreamSession: reject the connect promise when 'task-finished' arrives
before 'task-started' instead of hanging forever in 'transcribing'.
- Remove dead keyterm enrichment (project/branch/recent-file paths were
unreachable after the privacy fix) and the now-inert CJK echo guard, plus the
tests that asserted that removed behavior; fix the misleading "OpenAI prompt
field" comment.
- Add the missing 'Voice Model' and macOS mic-permission i18n keys to
en/zh/zh-TW (they were falling back to English; check-i18n doesn't flag keys
absent from en).
Co-authored-by: Qwen-Coder <[email protected]>
* fix(voice): reject partial stream transcripts
* fix(cli): let dialogs consume voice keys first
* fix(voice): reject incomplete qwen realtime transcripts
* fix(voice): handle stream review blockers
* fix(voice): salvage qwen realtime transcript on close
* fix(voice): sanitize streamed transcript text
* test(audio): run audio capture tests in CI
* fix(voice): address review blockers
* fix(voice): report stream close while recording
* fix(voice): reduce keyterm echo false positives
* fix(voice): handle realtime close diagnostics
* fix(voice): address realtime review blockers
* fix(lint): allow legacy voice filenames
* chore(cli): rename voice files to kebab-case
---------
Co-authored-by: Qwen-Coder <[email protected]>
|
||
|
|
be31e8493c |
perf(filesearch): move AsyncFzf index construction to a worker thread (#4621)
* perf(filesearch): move AsyncFzf index construction to a worker thread
`new AsyncFzf(allFiles, ...)` is misleadingly named — its constructor is
synchronous and dominates the @-picker's main-thread cost on workspaces
with >20k files (where the v2→v1 algorithm switch kicks in). It's the
remaining freeze source after PR #3214 moved the crawl onto async spawn-
based git ls-files / ripgrep / fdir.
This change hosts the AsyncFzf instance in a worker_threads worker so the
construction happens off the main thread; only completed find() results
cross the message channel. Below ~5k files the in-thread path is kept
because worker spawn + IPC overhead exceeds the construction cost there.
Notes:
- `FzfWorkerHandle.create()` awaits the worker `ready` event before
returning, so init errors surface at create() time rather than as a
surprise on the first find().
- Worker is `unref()`-ed so a mid-find @-picker doesn't pin process exit.
- `FileSearch` interface gains an optional `dispose?(): Promise<void>`
to release worker resources. `FileMessageHandler.clearFileSearchCache`
calls it so the long-running VS Code extension host doesn't accumulate
workers across watcher-triggered evictions.
- `installInProcessFzfTransport()` is provided for tests / sandboxed
environments that need to skip worker spawn entirely.
- Bundle gains a second esbuild build that emits `dist/fzfWorker.js`
alongside `dist/cli.js`; `prepare-package.js` ships it in the tarball.
- In `npm run dev` (tsx) the bundled worker file isn't on disk, so the
handle silently falls back to in-thread — acceptable for a dev script.
Closes the door on #3455's two open `FileIndexService` review items by
design: there is no `whenReady()` (handle awaits ready inside `create()`)
and there is no in-worker crawl (crawl stays on main, only fzf moves).
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
* fix(filesearch): address review feedback on fzf worker PR
- Fix worker thread leak in useAtCompletion: dispose previous FileSearch
instance on re-init and component cleanup
- Fix worker leak on init failure: wrap inst.ready() in try/catch with
timeout, dispose on failure
- Fix find()-after-dispose hang: reject immediately when worker is disposed
- Add limit field to FindMessage protocol to cap IPC payload before
postMessage serialization
- Add real worker_threads test coverage (spawn/find/dispose lifecycle,
concurrent finds, init failure, post-dispose rejection)
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* fix(filesearch): address second round of review feedback
- Fix useAtCompletion cleanup: move dispose() to [cwd,config] effect so
it only fires on workspace change, not every keystroke/status transition
- Clear setTimeout in Promise.race on happy path to prevent timer leak
- Dispose FileSearch instances in FileMessageHandler on panel close
- Fix incorrect comment in prepare-package.js (resolveBundleDir, not URL)
- Add worker crash test: verify failAll rejects pending find() on exit
- Add RecursiveFileSearch.dispose() test in fileSearch.test.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* fix(core): close missing braces in formatDateForContext test
The second test case in the formatDateForContext describe block was
missing its closing `});`, causing a TS1005 parse error that broke CI
on all platforms. This was introduced upstream in
|
||
|
|
61d91ad716 |
fix(build): tree-shake React reconciler dev build to prevent PerformanceMeasure leak (#4462)
The ink 6→7 upgrade (v0.15.11) pulled in react-reconciler 0.33, whose development build calls performance.measure() on every component render. Since NODE_ENV was never set to "production" in the esbuild define map, the bundle shipped both dev and prod builds and selected dev at runtime, causing an unbounded measureEntryBuffer leak (~45% of heap after moderate use, confirmed via heap snapshots). Set process.env.NODE_ENV to "production" at build time so esbuild statically resolves the conditional require and tree-shakes the entire 15k-line dev build. Bundle shrinks by ~700 KB / 15,800 lines. |
||
|
|
9d20536343 |
perf(cli): code-split lowlight to cut startup V8 parse cost (#4070)
* perf(cli): code-split lowlight to cut startup V8 parse cost
Move the syntax-highlight engine out of the synchronously-parsed cli.js
entry into a separately-emitted chunk and load it via dynamic import on
the first code-block render. Until the chunk arrives, code blocks render
as plain text; the next React commit of the surrounding subtree picks up
the highlighted version, so users never see incorrect highlighting –
just an imperceptibly later transition for the very first code block.
Mechanics:
- esbuild config: switch entry to outdir + splitting:true so that
`await import('lowlight')` produces an actual on-disk chunk that's
only parsed by V8 when first needed.
- esbuild-shims: rename injected __dirname/__filename to qwen-prefixed
symbols + use `define` to redirect free references. Previous inject
collided with vendored libraries (yargs) that ship their own
`var __dirname` ESM-compat polyfill once splitting flattens chunks.
- prepare-package: include the new chunks/ directory in the published
package's files list.
- CodeColorizer: keep the public colorize{Code,Line} signatures and HAST
rendering identical; on first call when the chunk hasn't loaded it
returns the plain line and fires the dynamic import via a tiny
standalone loader module.
- lowlightLoader (new): isolates the lazy-load surface to a module with
zero transitive imports (no themeManager, settings, or core). This
lets test-setup prime the cache without dragging the whole UI module
graph into every test file, which was observed to perturb theme and
settings test outcomes when CodeColorizer was imported directly.
- test-setup: await loadLowlight() once via the standalone loader so
synchronous snapshot tests see the highlighted output deterministically.
Measurements (real $HOME, n=15 interleaved A/B vs main HEAD, macOS):
| Metric | Before (mean±sd ms) | After (mean±sd ms) | Δ | t | p |
| ------------------ | ------------------- | ------------------ | -------- | ------ | -------- |
| firstByte (wall) | 1633.5 ± 88.7 | 1475.8 ± 73.3 | -157.7 | 5.31 | 1.33e-5 |
| idle (wall) | 2048.7 ± 93.6 | 1902.3 ± 80.2 | -146.3 | 4.60 | 8.71e-5 |
| cli.js size | 25 MB | 6.9 MB | -18.1 MB | — | — |
Both metrics clear the +50ms-or-10% Welch's t-test bar by an order of
magnitude. cli.js drops 72%; total payload (cli.js + chunks/) is
similar but only cli.js is parsed at module-eval time, which is the
phase that dominates the user-visible startup gap.
How to validate:
npm run bundle
ls dist/ # cli.js + chunks/lowlight-*.js
node dist/cli.js -y # interactive UI still renders
Generated with AI
Co-authored-by: Qwen-Coder <[email protected]>
* fix(cli): resolve chunk-relative sibling paths under esbuild splitting
With `splitting: true`, esbuild hoists modules with shared dependencies
into `dist/chunks/`. Three modules derived runtime paths from
`import.meta.url` assuming they were co-located with `cli.js`; once
hoisted, `path.dirname(fileURLToPath(import.meta.url))` resolved to
`dist/chunks/` and sibling-asset lookups silently missed:
- `skill-manager.ts`: bundledSkillsDir → `dist/chunks/bundled` (actual
`dist/bundled/`). The `existsSync` guard swallowed the miss, dropping
all four bundled skills (`/review`, `/qc-helper`, `/batch`, `/loop`)
with no user-visible signal.
- `ripgrepUtils.ts`: `getBuiltinRipgrep()` → `dist/chunks/vendor/...`.
Falls back to system rg if installed, otherwise null on minimal
hosts — degrading grep to the slow internal scanner.
- `i18n/index.ts`: `getBuiltinLocalesDir()` → `dist/chunks/locales`.
User-visible behavior survives via the static glob import in
`tryImportBundledTranslations`, but the loose-on-disk override path
is dead.
Each module now strips a trailing `chunks` segment when present, so
the lookup resolves under `dist/`. In source / transpiled modes the
basename is never `chunks`, so the fallback is a no-op.
Also:
- Add `chunks` to `DIST_REQUIRED_PATHS` in `create-standalone-package.js`
so a regressed bundle that produces only `cli.js` fails the
pre-packaging check instead of shipping a broken archive.
- Expand `esbuild-shims.js` header so future contributors understand
that `__qwen_filename` / `__qwen_dirname` always resolve to the
shim's chunk file (dist/chunks/) and that sibling-asset lookups
must strip the `chunks` segment.
Reported by claude-opus-4-7 via Qwen Code /qreview on #4070.
Generated with AI
Co-authored-by: Qwen-Coder <[email protected]>
* perf(cli): prefetch lowlight from AppContainer + harden loader
Three follow-ups to the lowlight code-split:
- AppContainer fires `loadLowlight()` from a mount effect so the dynamic
import is already in flight before any code block needs colorizing.
Without this, code blocks committed to ink's append-only `<Static>`
region before the import resolves stay plain text for the rest of
the session — Static can only be re-rendered via `refreshStatic`,
which is not wired to lowlight load completion. Common reachable
paths: short `--prompt -p` runs that finalize quickly, Ctrl+C-
cancelled first turns, and the first-paint history replay on
`--resume`. The startup parse-cost win is preserved (V8 still
parses off the critical path).
- `lowlightLoader.ts` latches the first import failure so subsequent
calls short-circuit to a rejected promise instead of re-attempting
`import('lowlight')` on every keystroke. The colorizer already falls
back to plain text on miss; recovery requires a fresh process anyway.
- `test-setup.ts` wraps the top-level `await loadLowlight()` in
try/catch. A transient import failure no longer crashes the entire
vitest run — tests that hit a code block render the plain-text
fallback and surface a warning.
- `CodeColorizer.tsx` header comment updated to point at the
AppContainer prefetch instead of claiming first-paint always sees
a loaded instance.
Reported by DeepSeek/deepseek-v4-pro and claude-opus-4-7 via Qwen Code
/review and /qreview on #4070.
Generated with AI
Co-authored-by: Qwen-Coder <[email protected]>
* refactor(bundle): extract resolveBundleDir helper, apply to extensions/new
Centralises the `chunks/` strip pattern that three sites
(`i18n/index.ts`, `skills/skill-manager.ts`, `utils/ripgrepUtils.ts`)
each duplicated after the round-3 fix in
|
||
|
|
cadda23782 |
chore(deps): upgrade ink 6.2.3 → 7.0.2 + bump Node engine to 22 (#3860)
* chore(deps): upgrade ink 6.2.3 -> 7.0.2 + bump Node engine to 22
ink 7 requires Node >=22 and react-reconciler 0.33 with React >=19.2,
so this PR also bumps:
- Node engines (root + cli + core) 20 -> 22
- React/react-dom 19.1 -> 19.2.4 (pinned exact via overrides to keep
the transitive React graph deduped to a single instance)
- @types/node pinned to 20.19.1 via overrides to avoid an unrelated
Dirent NonSharedBuffer regression in sessionService tests
- @vitest/eslint-plugin pinned to 1.3.4 to avoid an unrelated lint
regression introduced by the 1.6.x rule additions
- react-devtools-core 4.28 -> 6.1 (ink 7 peerOptional requires >=6.1.2)
- ink hoisted to root devDeps so workspace-private peer-dep contention
doesn't push ink-link/spinner/gradient into nested workspace
installs (which would skip transitive resolution for terminal-link)
Workflow + image + installer alignment:
- .nvmrc 20 -> 22
- Dockerfile node:20-slim -> node:22-slim
- CI test matrix drops 20.x (keeps 22.x + 24.x)
- terminal-bench workflow Node 20 -> 22
- Linux/Windows install scripts upgrade their Node version targets
Documentation alignment:
- README.md badge + prerequisites
- AGENTS.md, CONTRIBUTING.md, docs/users/quickstart.md,
docs/users/configuration/settings.md, docs/developers/contributing.md,
docs/developers/sdk-typescript.md, docs/users/extension/extension-releasing.md,
packages/sdk-typescript/README.md, packages/zed-extension/README.md,
scripts/installation/INSTALLATION_GUIDE.md
Test gating:
- Two AuthDialog/AskUserQuestionDialog tests that drive <SelectInput>
through ink-testing-library now race ink 7's frame-throttled input
delivery and land on the wrong option. The maintainers had already
marked one of them unreliable (skip on Win32 + CI+Node20). Extend
that gate to cover all environments until upstream
ink-testing-library ships an ink-7-compatible release that flushes
input deterministically. The other test now uses it.skip with the
same comment. No business code changes.
Verified locally:
- npm run typecheck across all workspaces: clean
- npm run lint (root): clean
- npm run test --workspaces:
cli 312/312 files, 4918 passed, 9 skipped
core 266/266 files, 6836 passed, 3 skipped
webui 6/6, 201 passed
sdk 40/40, 283 passed, 1 skipped
- npm ls ink: single [email protected] instance across all peer deps
- single [email protected] instance
Generated with AI
Co-authored-by: Qwen-Coder <[email protected]>
* chore: align Node 22 floor across all shipping artifacts
Reviewer (tanzhenxin) flagged five surfaces where the >=22 engine bump
leaked: SDK package metadata, web-templates engines, /doctor runtime
check, main bundler target, and SDK bundler target. Each was a separate
escape hatch letting Node 18/20 consumers install or run the artifact
on an unsupported runtime.
- packages/sdk-typescript/package.json: engines.node >=18.0.0 -> >=22.0.0
- packages/web-templates/package.json: engines.node >=20 -> >=22
- packages/cli/src/utils/doctorChecks.ts: MIN_NODE_MAJOR 20 -> 22
- esbuild.config.js: target node20 -> node22 (main CLI bundle)
- packages/sdk-typescript/scripts/build.js: target node18 -> node22 (esm + cjs)
- packages/cli/src/utils/doctorChecks.test.ts: rename test label to v22+
Generated with AI
Co-authored-by: Qwen-Coder <[email protected]>
* ci(e2e): bump E2E workflow Node matrix to 22.x
Reviewer (tanzhenxin) flagged that e2e.yml still pinned node-version
20.x while root engines is now >=22, so every E2E run on push would
either fail at npm ci with engine error or silently exercise the bundle
on a runtime that's no longer in ci.yml's test matrix.
The macOS job in the same workflow already reads .nvmrc (which is 22)
so this only updates the Linux matrix.
Generated with AI
Co-authored-by: Qwen-Coder <[email protected]>
* fix(deps): drop root wrap-ansi override so ink 7 gets its declared dep
Reviewer (tanzhenxin) flagged that the root overrides.wrap-ansi: 9.0.2
predates this upgrade and forces every consumer (including ink) to v9,
while ink 7 declares wrap-ansi: ^10.0.0. The lockfile had no nested
install under node_modules/ink/, so ink 7 was running with a transitive
dep one major below its declared minimum.
Dropping the global override lets ink resolve its own wrap-ansi 10
nested install (now visible in the lockfile under
node_modules/ink/node_modules/wrap-ansi), while the cli package's own
direct `wrap-ansi: 9.0.2` dependency keeps the cli code path
(TableRenderer.tsx) on the version it has been tested against. The
nested cliui override is preserved for yargs which still needs v7.
Verified via `npm ls wrap-ansi`:
- [email protected] -> [email protected] (newly nested)
- @qwen-code/qwen-code -> [email protected] (unchanged)
- yargs/cliui -> [email protected] (unchanged)
Generated with AI
Co-authored-by: Qwen-Coder <[email protected]>
* test(InputPrompt): un-skip placeholder ID reuse after deletion
Reviewer (tanzhenxin) flagged that the new it.skip on the
'should reuse placeholder ID after deletion' test was undisclosed in
the PR description and removed coverage of real product behavior
(freePlaceholderId / bracketed-paste backspace path) without a
TODO(#NNNN) link.
Their argument was sound: the skip rationale pointed at ink 7's input
throttle, but this same file just bumped the wait helper from 50ms to
150ms specifically to give ink 7 frame time. Re-running the test under
the bumped wait shows it passes reliably (5/5 runs in the full-file
context, 9/10 alone), so the skip was masking the throttle-flake that
the wait bump already addresses, not a real product bug.
Drop the it.skip and the now-stale comment so coverage of the
freePlaceholderId reuse logic is restored.
Generated with AI
Co-authored-by: Qwen-Coder <[email protected]>
* test(InputPrompt): bump first prompt-suggestion test wait to 350ms
The "accepts and submits the prompt suggestion on Enter when the buffer
is empty" test is the first in its describe block, so it pays the
renderer cold-start cost. On macOS-22.x CI runners that pushes the
Enter → onSubmit microtask past the default 150ms post-Enter wait. Match
the 350ms initial render wait used immediately above to absorb the cold
start.
* Revert "test(InputPrompt): bump first prompt-suggestion test wait to 350ms"
This reverts commit 6add83b62ea80c551c81f54af1fda3e6e7478f55.
* test(InputPrompt): wait for followup suggestion debounce before pressing Enter
Root cause of the failing prompt-suggestion tests on macOS and Windows
CI is not flaky timing of the test post-Enter wait — it's the 300ms
debounce inside createFollowupController.setSuggestion (shared core).
The Enter handler reads followup.state.isVisible synchronously, so if
the debounce timer has not fired before stdin.write('\\r'), the
suggestion path is skipped and onSubmit never runs. No amount of
post-Enter wait can recover from that — the keypress was already
processed against stale state.
The original wait(350) only left ~50ms margin over the 300ms debounce,
which ink 7 / React 19.2 mount overhead consumed on slow Windows
runners. Bump the initial wait to 700ms (named SUGGESTION_VISIBLE_WAIT_MS)
to give the debounce timer + cold-start render a generous buffer.
Apply to the two sibling tests too — without the wait their "does not
accept" assertions pass trivially when suggestion is never visible,
which is a false green that hides regressions in the actual reject path.
* fix(deps): align cli wrap-ansi with ink 7 (9.0.2 -> ^10.0.0)
Ink 7 ships its own wrap-ansi@10. CLI's direct dep was pinned to 9.0.2,
causing two copies of wrap-ansi in node_modules and a potential drift in
CJK width / ANSI handling between ink's internal text wrapping and our
TableRenderer.
Upgrading the CLI's direct dep to ^10.0.0 lets npm dedupe to a single
wrap-ansi@10 used by both ink and TableRenderer. API surface is
identical; the only documented behaviour change is that tabs are
expanded to 8-column tab stops before wrapping, which TableRenderer
doesn't feed in.
TableRenderer test suite (43 tests) passes against wrap-ansi@10.
Generated with AI
Co-authored-by: Qwen-Coder <[email protected]>
* chore(deps): document @types/node 20.x pin in overrides
The override pinning @types/node to 20.19.1 (while engines require
Node >=22) is intentional: bumping to @types/[email protected] re-introduces
a Dirent<NonSharedBuffer> type regression that breaks
@qwen-code/qwen-code-core/sessionService tests.
Add a sibling "//@types/node" note inside `overrides` so future
maintainers see the rationale and know when to revisit the pin
without having to dig through PR #3860 history.
Generated with AI
Co-authored-by: Qwen-Coder <[email protected]>
* test(AskUserQuestionDialog): link skipped Submit-tab test to tracking issue
The 'shows unanswered questions as (not answered) in Submit tab' test
was switched to `it.skip` in the ink 7 upgrade because
`[email protected]` doesn't flush input deterministically
through ink 7's 30fps throttle.
Add a `// TODO(#4036):` marker so the skip is greppable and can be
re-enabled once upstream ships an ink-7-compatible release.
Refs #4036
Generated with AI
Co-authored-by: Qwen-Coder <[email protected]>
* fix(deps): move @types/node pin comment out of overrides block
npm's `overrides` field requires every key to be a real package name —
the `"//@types/node"` comment-key added in 205855875 trips Arborist with
"Override without name" and breaks `npm ci` across all CI jobs.
Move the explanation to a sibling top-level `"//overrides"` key, which
npm ignores at the document root. Same documentation value, no
override-parser collateral damage.
---------
Co-authored-by: 秦奇 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
|
||
|
|
44c596cd14 | feat: add wasm build config (#2985) | ||
|
|
e03144ed34 |
fix: resolve punycode to userland package and skip env var test in sandbox
- Resolve punycode to userland package instead of deprecated node:punycode built-in to avoid deprecation warnings - Skip cron-tools env var test in sandbox mode since Docker containers don't receive environment variables set in the test process Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
3eedc43238 |
feat(channels): add Telegram channel integration with ACP bridge
Implements the channels infrastructure for connecting external messaging platforms to Qwen Code via ACP. Phase 1 supports plain text round-trip: Telegram user sends message -> AcpBridge -> qwen-code --acp -> response back to Telegram. New packages: - @qwen-code/channel-base: AcpBridge, SessionRouter, SenderGate, ChannelBase - @qwen-code/channel-telegram: TelegramAdapter using telegraf CLI: `qwen channel start <name>` reads from settings.json channels config, spawns ACP agent, connects to Telegram via polling. |
||
|
|
b488126830 | feat add multi-platform support | ||
|
|
35c865968f | chore: remove tiktoken dependency and replace with character-based token estimation | ||
|
|
f8be8a61c8 | 🐛 Bug Fixes Release v0.1.1 (#898) | ||
|
|
be633a80cc | 📦 Release qwen-code CLI as a Standalone Bundled Package (#866) | ||
|
|
eb95c131be | Sync upstream Gemini-CLI v0.8.2 (#838) | ||
|
|
ee4feea006 |
chore: consistently import node modules with prefix (#3013)
Co-authored-by: N. Taylor Mullen <[email protected]> |
||
|
|
f1575f6d8d |
feat(core): refactor shell execution to use node-pty (#6491)
Co-authored-by: Jacob Richman <[email protected]> |
||
|
|
1a2906a8ad | Revert #6088 (#6328) | ||
|
|
980091cbc2 | feat(core): refactor shell execution to use node-pty (#6088) | ||
|
|
13b0971291 |
fix(ci): Fix failing release workflow by creating a self-contained bundle (#4888)
Co-authored-by: Jerop Kipruto <[email protected]> |
||
|
|
820105e982 |
Safer Shell command Execution (#4795)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: N. Taylor Mullen <[email protected]> |
||
|
|
fb0db2dfd6 | adh/bugfix/1563 (#4822) | ||
|
|
8d9dc44b71 | Revert "Update to yargs v18 (#3759)" (#4252) | ||
|
|
2862ae7344 | Update to yargs v18 (#3759) | ||
|
|
cc7459e403 | fix version release for Dockerfile build (#1080) | ||
|
|
5bab5a7378 | fix version set for cli and slash commands (#892) | ||
|
|
37edbd8c18 | Rollforward AST changes to unblock Sandboxing (#863) | ||
|
|
584286cfd9 |
fix(deps): externalize tree-sitter (#840)
Submitting without approval to fix broken deployment on main. But also, we should lock this down. |