Commit Graph
54 Commits
Author SHA1 Message Date
chencheng (云谦) 1b1f4455d9 ci: build the PR tree once and shorten the required path (#16450)
* ci: build the PR tree once and shorten the required path

The no-AK integration gate, the four web-shell smoke shards and Lint & Static each rebuilt the tree test_build already uploads, and the build cache rarely helped: most runs logged "29 of 29 workspaces changed" because the arch-keyed cache was split across the mixed x64/ARM64 pool and a prefix restore returned the newest entry instead of the base commit's.

- test_build restores the build its base commit saved (ci-build-cache.mjs key --at), drops runner.arch from the key, and runs the serve fast-path closure and core subpath checks against the built tree.
- The no-AK integration gate unpacks that tree and runs as three shards; integration_no_ak_result keeps the required check name.
- The web-shell smoke unpacks the same tree instead of building; consumers recopy the opentui native assets for their host (host-assets).
- The standalone runtime regression moves from unit shard 1 to shard 4.
- A java_only profile skips the TypeScript lanes for changes confined to the Java packages; Lint & Static runs the scripts/tests suites that name them.
- Drafts skip the integration gate and the browser smoke; ready_for_review starts the full run.

* ci: check the settings schema in test_build, which has the core dist

Lint & Static only had core's dist because the serve fast-path check built
it; with that check moved, Generate settings schema failed to import it.

* ci: keep the standalone regression on unit shard 1

Moving it did not shorten shard 1: the regression runs in the side lane,
in parallel with the CLI and core suites, and shard 1's time is its CLI
slice (576 s against 147 s on shard 4 for the same 181 files). That
imbalance is left to duration-based sharding.
2026-11-06 03:30:11 -08:00
56fec7f3f5 ci: shard the Linux unit tests and web-shell smoke, and reuse cached builds (#16133)
* ci: shard the Linux unit-test lane eight ways behind one build

The Linux Test job ran 45-59 minutes: a ten-minute prepare build, then the CLI and core suites back to back. A new test_build job runs the prepare build once and uploads its outputs; eight shards install without the build, unpack them and run --shard=N/8 of every Vitest suite. Test (ubuntu-latest, Node 22.x) becomes an aggregate that passes only when the build and every shard pass.

* ci: record ci.yml's growth from the test_build prelude

test_build carries the same pre-checkout cleanup, checkout and install prelude as the test shards; ci-platform-lanes.test.js pins the two copies identical.

* ci: record the Linux lane's two new jobs in the PR-stage budget

test_build and the test_ubuntu aggregate add two jobs; the declared timeouts drop from 305 to 295 minutes.

* ci: shard the web-shell E2E smoke four ways

The smoke lane ran 44-46 minutes on every recent PR, about 33 of them in the browser tests at two workers, and is the longest lane once the Linux unit tests are sharded. Each shard runs --shard=N/4 of the smoke; the transcript gate runs on the first. web-shell E2E Smoke (ubuntu-latest, Node 22.x) becomes an aggregate over the shards.

* ci: bound the unit shards' tree download and retry it once

A pool host stalled 17 minutes in download-artifact while the other shards took under a minute.

* ci: rebuild only the workspaces a change reaches

scripts/ci-build-cache.mjs hashes each workspace's tracked build inputs (tests excluded) with its workspace dependencies' hashes and the shared root inputs. test_build and the web-shell smoke shards restore the newest cached build and rebuild only workspaces whose hash changed. A push to main always builds from scratch and saves, so a pull request starts from a clean build of main.

* docs(ci): describe the incremental build cache

* ci: keep the build cache path the same on every pool host

actions/cache versions an entry by its path; under runner.temp the path differs per host, so a run could never restore another host's save.

* ci: save the ARM64 build cache from the smoke lane, and run the aggregates on the shards' runners

The smoke shards run on the ARM64 pool, which test_build may not, so the first shard saves the clean build it made when its restore found nothing. The two aggregate checks queued 9 and 13 minutes for a hosted runner after their shards finished; they now take the shards' runners.

* ci: bound the sharded test step and skip shards when the build fails

Address the first automated review round on the Linux sharding lane:

- Gate every build-consuming shard step on `test_build` success. `!cancelled()`
  stays true when a needed job fails, so a failed build still started all eight
  shards, each paying a full install before dying at the missing artifact and
  reporting artifact plumbing as its failing step.
- Drop the test step cap to 45 minutes. The job cap is 60, so the old 110 could
  never fire; a step timeout keeps the diagnostic steps below it alive while a
  job timeout does not.
- Keep the built-tree artifact for a week so a late "re-run failed jobs" still
  finds it, and correct the trigger comment that still named three post-merge
  jobs when five now accept push.

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

* ci: keep disk telemetry for a failed build and correct the design doc

`test_build` installs with the disk sampler and then builds for up to its
whole timeout, but the sampler dies with the install step's trap and no
collector ran, so an ENOSPC build (or the OOM that took a shard host) kept
no evidence. Give it the same failure-gated dump and last-step upload as
`lint_and_static`, under its own artifact name.

The incremental-lint design still said the test job keeps normal prepare
behaviour; this lane now installs with `QWEN_SKIP_PREPARE=1` and unpacks
the tree built once by `test_build`. Correct both language versions.

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

* ci: share the built-tree definition and the build preflight, harden the guards

The archive step re-implemented `builtPaths()` from `scripts/ci-build-cache.mjs`,
so the tree the shards unpack had a second definition that could drift from the
one `pack` stores. A `paths` mode now prints that list for `tar -T -`, and a new
test pins the four artifact-name literals that tie the producer to the eight
consumers.

The cached branch of `build` also hand-reproduced a subset of `scripts/build.js`
and so skipped its ink-patch preflight: the same missing
`frame-controller.d.ts` self-healed on a cache miss and surfaced as ~50 tsc
errors inside ink's declarations on a hit. The preflight is now a function
reachable as `scripts/build.js --preflight` and runs before both branches.

The remaining review suggestions were all meta-test gaps: the standalone-runtime
expression and the `test_build`/`test` prelude were guarded by nothing that
compares them in order, `ci-build-cache.mjs`'s three CI modes were never
executed by a test, and the infra-classification filter's step name was not
joined to `ci.yml`. Each now has an assertion that goes red when the guard is
removed.

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

* ci: bound the build steps so the failure collectors can run

Round-2 review found four issues in the previous round's fixes and one gap
in the new build tests.

A job timeout cancels the job and skips the failure-gated collectors, so the
`test_build` steps that can block now carry caps below the job's 45 minutes:
the install, the build, the pack and the archive. The install caps are
identical across `test_build`, `test` and `lint_and_static` so the prelude
parity guard still compares the three copies field for field. A test pins
every blocking cap under the job ceiling.

The `--preflight` early exit was only asserted as a string, so it could be
renamed with nothing going red while the cache-hit path silently ran the
full build. A spawned case now copies `scripts/build.js` into the fixture
and asserts the flag exits without generate, a workspace build or the
schema step.

The CLI mode tests write extension-less `#!/bin/sh` stubs and join PATH with
':', which cmd.exe cannot resolve; the Windows lane collects that file and
downgrades its failure to a warning, so it would have carried a permanently
red block. The suite is excluded on win32 alongside the other bash-fixture
suites.

The `test_build`/`test` intersection grew to 16 with the disk collector, but
the floor stayed at 15, leaving exactly one shared step uncompared; the floor
now matches the membership. The cache-hit-with-changes branch — the code
that decides what is rebuilt and what is deleted — gains a case: a changed
workspace is purged and rebuilt by directory, and an untouched workspace
keeps what the cache restored.

ci.yml's recorded size baseline is updated to the current file so the next
PR gets the full growth allowance back.

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

* test(ci): keep cache fixture call logs outside the archived tree

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

---------

Co-authored-by: 易良 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-11-04 14:52:12 +08:00
易良 dff7deb19c docs(release): explain hosted-runner queueing for publish (#16194)
* docs(release): explain hosted-runner queueing for publish

* docs(release): link the current release pool record
2026-11-03 22:41:31 +08:00
ae7901552e fix(core): settle the loop reminder delivery at the model-acceptance boundary (#15559)
* fix(core): settle the loop reminder delivery at the model-acceptance boundary

takeLoopReminder() cleared the pending map and logged the 'reminded'
telemetry while the ToolResult request was only being assembled. Between
the take and turn.run(), the send still awaits microcompaction and
memory recall and yields pending goal events, so a consumer that stops
the generator there loses the streak's warning (dropPendingReminders
only unmarks still-pending keys) while telemetry credits a delivery the
model never saw.

The delivery is now only prepared at the take: the client commits it
when the first model-accepted event arrives and abandons it — restoring
the pending entries — in the send's finally when the request never
reached the model. A full reset drops a prepared delivery so a late
abandon cannot leak a stale warning into a new prompt. The agent-core
round loop keeps its take-time accounting with an explicit commit since
it has no consumer-visible suspension between the take and the model
call.

Fixes #15537

* fix(core): defer the agent-core loop reminder commit to model acceptance

* fix(core): drop the ineffective pre-round reminder abandons

The three abandonLoopReminderDelivery() calls at the abort/maxTurns/time
checks could not change anything: each is followed by a break out of the
round loop, the detector is method-local, and abandon only moves entries
between two private maps without logging. They made the take-site comment
and the maxTurns control read as if they were what keeps `reminded`
truthful, so drop them and say what the control actually pins — no round
crosses a model-acceptance boundary, so nothing commits.

Also, from the same review round:

- abandon's doc block named the wrong function. A retry drops the streak
  through resetToolCallCount(), which deletes the three hard-coded keys;
  dropPendingReminders() cannot reach entries already taken out of the
  pending map.
- client.test.ts asserted not.toHaveBeenCalled() immediately after
  mockClear(), which cannot fail. Compare the call count with the value
  captured before the send instead, so the "suspended before turn.run"
  premise is actually pinned.
- cover the multi-entry path: commit credits every entry of a delivery and
  abandon restores all of them, not just the first.
- telemetry.md and both languages of the loop-protection design doc now
  say `reminded` is logged at model acceptance, and the "sent, never
  accepted, credited on a later send" case is added to the enumeration of
  not-counted cases.

---------

Co-authored-by: suzhiguo7 <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
2026-11-02 00:43:23 +08:00
Shaojin Wen eea44208b8 ci: run every suite but CLI and core in the side test lane (#15570)
* ci: run every suite but CLI and core in the side test lane

The Linux test job's main lane ran the CLI suite and then, one after
another, core, about twenty smaller workspace suites and scripts/tests,
while the side lane (web-shell, then the standalone regression) went idle
long before the CLI suite finished. The main lane now runs only the CLI
and core suites; the side lane runs every other workspace's test:ci, the
standalone regression and scripts/tests, each at half the Vitest worker
cap. Every side-lane suite runs even after another fails.

* ci: print the side lane of a test step killed before its replay

The lane script replays the side lane's log only after the main lane
ends, so a step timeout or cancel lost every suite but the CLI and core
ones. The script now writes that log to SIDE_LANE_LOG when the caller
names one; ci.yml names it under runner.temp, outside the TMPDIR the step
removes, and a failure()/cancelled() step prints whatever is left of it.

The lane test also checks that the main lane's output is present before
it checks its order against the replay, and a comment that still placed
scripts/tests in the main lane now names the side lane.

* ci: keep the lane script's log out of a nested run

scripts/tests runs `npm run test:scripts:unit`, which runs the lane script
again, inside the step that names SIDE_LANE_LOG. The nested run inherited
that name and truncated, replayed and removed the outer side lane's log, so
the outer replay had nothing to print. The script now unsets the variable
once it has read it, and the lane test drives a nested run with a named log
to pin that: the nested run must not see the caller's log, and the outer
side lane's own output must still reach its replay.

The print-step test also pins the `if [ -f "${side_log}" ]` guard around the
cat, which is what keeps that step quiet on an ordinary red test job.
2026-10-27 00:01:58 +00:00
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
qqqys 7bda51a070 feat(core): add model.loopProtection and remind instead of stopping loops by default (#15016)
* feat(core): add model.loopProtection and remind before loop stops

Add the model.loopProtection setting (stop, warn, off). Under the default
stop, every stop point stays where it is; the always-on guards (identical
calls, shell inspection, daemon invalid tool parameters, stuck repetition
past the adaptive cap) first send the model one reminder with the next tool
results. warn only reminds, except for content and thought repetition; off
neither reminds nor stops. Budgets stop in every mode.

A new loop_protection telemetry event records each reminded, stopped and
observed decision. Loop stop hints and the TUI dialog note name the setting.

Refs #14828

* feat(core): default model.loopProtection to warn

A firing loop guard now reminds the model once and lets the turn continue
by default. Budgets still stop every mode, and content and thought
repetition still stops. Set model.loopProtection to "stop" to keep the
previous stop points.

Refs #14828

* fix(core): keep side queries out of the running turn's loop decisions

A concurrent /btw side query shares the client's loop detector. Its retry
dropped the turn's pending reminders, its stateful tool results could queue a
global-duplicate reminder into the turn, and under `off` it marked patterns
observed under the turn's prompt id. Route recordToolResult through the same
ownership gate, drop pending reminders once on the owner's Retry only, and
skip observed decisions for side queries.

Warn once when model.loopProtection is not a valid mode instead of silently
using the default, and update the consecutive-identical comment that still
described an unconditional halt.

* fix(core): keep reminders across side queries and guard unknown modes

A concurrent /btw side query starts as a UserQuery, so its reset cleared the
running turn's queued reminders and reminded/observed decisions. Under `warn`
the stuck turn then got no reminder at all. Keep that state on a side query's
reset, and log each reminder under the prompt id that queued it.

Validate model.loopProtection in core Config as well, since SDK and serve
embedders bypass the CLI check and an unknown mode acted as `off`.

For a stateful read tool with no recorded results, `warn` now records an
observed decision at the stop point instead of a reminder claiming the
result will not change; `stop` still fails safe and stops.

* fix(core): let late stateful results earn the warn reminder

Under `warn`, a stateful read that reached the stop point before its results
were recorded was observed under the 'identical' key, which then blocked the
reminder for the rest of the streak even after the results came back
unchanged. Record that case under its own key so the reminder can still be
sent once the evidence is in.

In daemon sessions, leave queued loop reminders untaken when the repeated
tool failure guard stops the batch, so they are not logged as `reminded`
without being sent.

Update the telemetry docs for `observed` under `warn`, note in
model.skipLoopDetection that model.loopProtection decides whether the
always-on guards remind or stop, and drop the stale DashScope #5019 rationale.
2026-10-21 13:11:16 +00:00
qqqys bf118a1220 feat(core): report why loop protection stopped a turn (#14858)
Two loop types cover two causes each: turn_tool_call_cap is an explicit
model.maxToolCallsPerTurn, the adaptive backstop or a stuck halt past
the soft cap, and global_tool_call_duplicate is the heuristic duplicate
detector or the duplicate provider tool-call id breaker. Each of these
stops now carries an additive loopReason, and cap stops carry the call
count and the limit that applied, on the LoopDetected event, the
loop_detected telemetry and the daemon turn_error. The SDK keeps the
detail on transcript blocks instead of dropping the loop type.

The headless CLI, the TUI loop dialog, OpenTUI and Web Shell (en and
zh-CN) explain each cause and what to change. No detector fires or
stops at a different point. The export renderer budget rises to
2,050,000 for the new i18n keys (+5,139 bytes measured).

Part of #14828.
2026-10-20 09:58:07 +00:00
Qiuand易良 5ded4b8c2a docs(telemetry): clarify OTLP HTTP uses JSON encoding (#14664)
Co-authored-by: 易良 <[email protected]>
2026-10-20 14:19:49 +08:00
烽宁 06efd789b1 feat(telemetry): Record reasoning effort on LLM spans (#14718) 2026-10-20 01:07:37 +08:00
5374641323 feat(memory): Opt-in selector skip for a unique new recall hit (#13158)
* feat(memory): opt-in extraction cooldown and selector skip experiments

Moved out of #13020, where they had been bundled with unrelated
deferred-tool work. Content is the #13020 head (ffd9df8928) for these
files and hunks, applied onto current main; history and review threads
for these changes live on #13020.

- #13004: after a no-op auto-memory extraction, an opt-in cooldown
  (QWEN_CODE_MEMORY_EXTRACT_NOOP_COOLDOWN_TURNS, off by default) skips
  extraction for a bounded number of turns while the prompt stays below
  the compaction warn threshold. Skipped turns stay inside the
  extractor's window (CACHE_SAFE_HISTORY_TAIL_ENTRIES), and a run that
  makes no progress advances the window instead of freezing it.
- #13003: an opt-in flag (recall-experiment.ts, off by default) skips
  the recall selector when there is a single strong hit; the result is
  delivered as the fast phase, and a low-cardinality selector_skipped
  attribute is added to the recall log and metrics.

Refs #13004
Refs #13003

* fix(core): Preserve the zero-tool extraction cursor guard

* fix(memory): Avoid extraction from runtime reminders

* fix(memory): Preserve user text across reminder parts

* fix(memory): Bind extraction cursor to history

* test(memory): Require exact same-length replacement

* fix(memory): Wait for queued tasks during drain

* fix(memory): Consume ineligible history tails

* docs(memory): Record tail context acceptance limit

* fix(memory): Recover reported writes after extraction failure

* docs(memory): Clarify best-effort refresh recovery

* fix(memory): Suspend repeated failed extraction windows

* fix(core): close the cooldown-arm and review-round findings on the memory PR

- The no-op cooldown now arms on a completed no-op whose cursor ADVANCED
  (extract.ts reports `cursorAdvanced`), not on reaching the live end —
  the windowed arm stops short of history.length by construction, so a
  tool-heavy session never armed it and the experiment saved nothing
  exactly where #13004 measured the waste (R1-1).
- A media-only user turn counts as user content for the extraction gate;
  an empty-text read advanced the cursor past a turn no extractor ever
  sees, and the cursor only moves forward (R1-11).
- The queued trailing extraction's rejection is observed with a warn log
  instead of vanishing into track()'s in-flight removal (R2-9).
- `selector_skipped` is stamped only on structured-mode recalls, where a
  skip decision exists; legacy recalls left the field at a constant false
  and mixed into the experiment's control series. The metric dimension
  attaches only when the knob is on AND the field is a boolean (R2-14).
- The docs now say so: the structured-recall contract (both languages)
  marks selector steps conditional under the knob, and the telemetry
  inventory lists selector_skipped with its gating conditions (R2-11).
- Pins: cooldown arm on a windowed no-op + held-cursor non-arm + arm-point
  accumulation across consecutive skips (manager.test.ts); media-only user
  turn + cursorAdvanced assertions (extract.test.ts); legacy-unset event +
  dimension drop (recall.test.ts, loggers.test.ts).

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

* fix(memory): Count pending backlog from the extraction cursor

* fix(memory): Flush pending extraction before compaction

* fix(memory): Narrow selector experiment and withdraw unsafe cooldown

* fix(memory): widen the #13003 skip guard, narrow selector_skipped

Two review findings on the opt-in skip-selector guard, each reproduced at
b4e0bba5b8 before fixing.

The uniqueness test counted only the published fast list -- the top
MAX_RELEVANT_DOCS of the lexical ranking -- while the selector it suppresses
is handed modelCandidates, whose lexical half is the top
MAX_MODEL_CANDIDATE_DOCS - RECENT_MODEL_CANDIDATE_RESERVE, roughly 36x wider.
A second strong title/keyword match ranked 6th or lower was therefore
invisible to the guard: an ambiguous recall skipped the selector and still
reported selector_skipped: true, landing in the treatment arm as a "unique"
strong hit. selectModelCandidateDocuments now also returns the strong-match
count over the lexical array it already scored. The count is computed only
while the guard is live, so knob-off recalls pay nothing on a path that
carries a latency budget, and the flag is now read once per recall so the
count and the guard cannot disagree.

logRecallResult also stamped selector_skipped: false on the empty-query /
empty-corpus / non-positive-limit short circuit, where the selector is never
called. Those trivially fast recalls joined the ablation's control arm, which
the treatment arm structurally cannot contain, and recordMemoryRecallMetrics
puts only strategy and selector_skipped on the counter and histogram, so the
samples could not be sliced back out. logRecallResult now takes the caller's
decision state and omits the field when no skip decision was made; the
heuristic fallback keys off selectorStartedAt so a selector failure still
reports false as a genuine control sample.

Both fixes are mutation-checked: reverting either one reds exactly the tests
added for it (1 failure and 2 failures respectively).

Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmurnh6s41i

* style(memory): let Prettier reflow the suppressed-selector assertion

Lint & Static failed at step "Run Prettier" on 34894db1d6 with
"[warn] packages/core/src/memory/recall.test.ts / Code style issues found
in 1 file". Prettier 3.6.1 reproduces it locally and --write produces this
two-line reflow of the `mock.calls[0]?.[2] ?? []` member chain; the
expression is unchanged, so this is formatting only.

Verified: prettier --check now passes on this file and on all 13 files the
branch changes. Test execution is left to CI (no node_modules in this
worktree).

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

* fix(memory): require a token boundary for a short title in the #13003 skip gate

R3-1: the skip gate made matchesTitleOrKeyword the final authority on whether
the model selector runs at all, but its title arm is an unbounded substring
test while the keyword arm already guards short Latin strings with a
token-boundary regex. A memory titled `ai` therefore matched the query
`explain provider fallback.`, and as the unique strong hit it suppressed the
selector: every other memory the selector would have added was lost, and the
recall entered the ablation's treatment arm as a genuine "unique strong hit"
when the match was a coincidence.

The injected document itself is not what this fixes. isStrongFastMatch keeps
the loose arm, so a coincidental short-title substring was and still is
published as a fast candidate; the refined selector result is filtered against
fastDeliveredRefs (core/client.ts:1656), so it can only add to the fast phase,
never retract it. What the gate changed is that the match also cancelled the
selector, and that is what is closed here.

Reproduced at 279c5440eb: the new test fails there with the selector never
called, and passes after the fix (88/88).

The guard is applied only at the gate, so lexical ranking and the default
(knob-off) path are unchanged and the design doc's "default behavior is
unchanged" claim still holds. The gate's title test reuses the keyword arm's
existing boundary rule instead of adding a second matcher; both design docs
now state it. lexicalStrongMatchCount deliberately keeps the loose arm and now
says so, because a looser count under-fires the skip, which is the safe
direction for an ablation.

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

* fix(memory): make the #13003 selector-skip gate require a real unique hit

The strict title arm delegated to `includesKeyword`, whose token-boundary
rule is gated on `/^[a-z0-9]{1,2}$/`, so for any Latin title of three or
more characters the strict and loose arms were byte-identical: a memory
titled `log` cancelled the model selector for a query containing
`catalog`. Apply the boundary rule to any single-word Latin title when
`requireTitleBoundary` is set; multi-word and non-Latin titles keep the
loose arm, and ranking callers are untouched.

The uniqueness count also ran over `lexical`, while the selector the gate
suppresses is handed `modelCandidates` — whose `recent` reserve is built
from `eligible`. A doc whose only strong-match evidence is a keyword that
is a proper substring of a query token scores 0, never enters `lexical`,
and so was never counted, yet it sat in the suppressed selector's pool.
Count the pool the selector actually receives, and rename the field to say
what it counts (title/keyword matches, deliberately not `isStrongFastMatch`
— that predicate's loose arm would empty the experiment's treatment arm).
Both changes only ever raise the count, so the gate skips strictly less
often.

State the real gate condition in both design docs, and pin
`selectorDecided` in both directions: each constant mutant of the
heuristic-fallback argument now fails a named assertion.

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

* fix(memory): require a token boundary for every non-CJK value in the #13003 skip gate

The strict arm keyed its boundary rule on value shape: `^[a-z0-9]+$` for a
title and `^[a-z0-9]{1,2}$` for a keyword. That left the coincidental inner
substring the gate exists to reject reachable through every other shape: a
keyword of three or more characters (`log` inside `catalog`, the collision the
gate's own rationale names), a multi-word Latin title (`log conventions` inside
`catalog conventions`), and a single-word non-ASCII title (`ток` inside
`поток`). Each suppressed the selector, dropped the refined phase over up to
200 candidates, and put a sample that is not a unique strong hit into the
ablation's treatment arm as `selector_skipped: true`.

Express the strictness once, on the script split this module's own tokenizer
already draws: a non-CJK value goes through `includesAtBoundary`, a CJK value
keeps `includes`, because a boundary rule needs a `[^\p{L}\p{N}]` neighbour
while Han, Kana, and Hangul are `\p{L}`. Both arms share it and it stays
behind the gate flag, so ranking callers and knob-off recall are unchanged.

Pinned by three tests that are red without the fix. The design-doc sentences
scoped the rule to single-word Latin titles; both twins now describe both arms.

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

* fix(core): preserve memory selector freshness and delivery phase

* fix(memory): reject concurrent document changes during reread

* fix(memory): tighten selector skip boundaries and log attributes

* fix(memory): clarify unstable reads and cover selector edges

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>
2026-10-04 07:48:44 +00:00
theater 7f06a1e082 docs(developers): document CI and release repository variables (#11129)
* docs(developers): document CI and release repository variables

* docs(developers): scope the worker-cap claim and note the retry tradeoff

Review feedback: state which Vitest invocations actually consume
QWEN_CI_VITEST_MAX_WORKERS, soften the retry-masking absolute, and point
at the workflow files as the source of truth for the defaults.

* docs(developers): attribute each CI pin to the test that actually holds it

Review feedback: release-workflow.test.js does not pin the release
worker-cap expressions; package-scripts.test.js asserts the
QWEN_CI_VITEST_MAX_WORKERS / ecs-qwen- expression, while
release-workflow.test.js pins the release retry and timeout expressions.
Name both files with their respective attributions.

* test(ci): keep documented variable defaults aligned with workflows

* test(ci): derive documented variables bidirectionally and fix web-shell scope note

* docs(ci): clarify variable scope and guard documented defaults

* fix(ci): format variable guard and clarify worker scope
2026-09-28 10:36:29 +00:00
qqqys b9840886b8 feat(core): record when each tool call started (#12517)
Tool timing frames in paged transcript replay carried only a duration:
the scheduler logs a batch's tool calls in one loop after every call in
it has settled, so a record's event timestamp is the batch's end and no
honest per-call start could be derived from it.

The start was always measured, though; it just was not kept. Record it
as an optional `started_at_ms` on the tool_call event, from the
scheduler's terminal states (which now keep `startTime`) and from both
places the ACP session logs a tool call, and pass it through unchanged:
replay emits it as the tool frame's `startedAt`, the SDK timing reader
keeps it, and the trajectory projection puts it on the tool row.

Nothing derives it. Records written before this carry none and their
frames stay start-less, as before. The trajectory fixture's two tool
starts came from a pre-merge build that did derive them, and are
removed so the fixture matches what a released daemon sends.
2026-09-23 04:45:52 +00:00
易良andQwen-Coder 64dd058c9c ci(pnpm): install with pnpm everywhere and retire package-lock.json (#11859)
* ci(pnpm): install dependencies with pnpm in CI and release

CI tests the graph release ships only if both install it the same way, so
this moves both, keeping every `npm run` script and `npm publish` as they are.

- ci.yml (7), release.yml (3) and release-vscode-companion.yml (2) install
  with `corepack pnpm install --frozen-lockfile`; release keeps
  --ignore-scripts plus its explicit postinstall and generate steps.
- setup-node drops the npm cache and sets package-manager-cache: false,
  which setup-node v5+ needs once package.json declares a pnpm
  packageManager.
- A composite action caches the pnpm store on the three always-hosted jobs;
  the ECS pool keeps its store on local disk.
- version.js no longer lets npm reify node_modules: --no-workspaces-update on
  both `npm version` calls, and a package-lock-only refresh in step 9.
- The notices generator, prepare-package's sharp pin and the standalone
  release's native package specs read the installed tree or pnpm-lock.yaml
  instead of package-lock.json keys.
- New size baselines for the three workflows, which were already over them.

* chore(vscode): regenerate NOTICES from the pnpm-installed tree

Generated in CI (pnpm Worktree Smoke on ubuntu-latest) by the ported
generator. Same entry set as before except the five packages npm and pnpm
resolve differently: fdir 6.5.0, picomatch 4.0.5, mdurl 2.1.0,
@types/node 22.20.1 and mime-db 1.52.0 (form-data's pinned copy). No entry
lacks a license text or repository.

* fix(pnpm): resolve undeclared zod peers to the repo's zod, as npm does

The first CI run on this branch failed the export renderer's size budget:
1,930,015 bytes against 1,930,000, where the same commit built on npm's
tree comes to 1,858,186. Comparing both trees' esbuild metafiles and
prebuilt bundles put the whole difference in web-shell's transcript.js
(+100,903 bytes pre-minify), and the added code is zod 4 plus the
@modelcontextprotocol/ext-apps schemas; the sdk daemon bundle is identical.

web-shell bundles ext-apps, whose zod and MCP SDK are peers, and declares
no zod itself, so pnpm filled the peer with the highest locked zod (4.4.3)
while npm serves the hoisted 3.25.76. Declaring zod 3.25.76 as a root
devDependency makes pnpm resolve every such peer to it
(resolve-peers-from-workspace-root is on by default), which is npm's
hoisting behaviour. mobile-mcp keeps its own zod 4.4.3, as under npm; the
npm tree does not change.

* fix(pnpm): keep npm's root @types/node so root-level typechecks match

The no-AK integration gate typechecks integration-tests/, which takes
Node's types from the root node_modules. npm hoists @types/node 20.19.1
there, because acp-bridge and core pin it; pnpm, with no root declaration,
hoisted 22.20.1, whose spawnSync overloads type process-registry.ts's
stdout as string | NonSharedBuffer and fail TS2322. The same code passes on
every npm-installed PR.

Declaring 20.19.1 as a root devDependency puts npm's copy back where a
root-level tsconfig looks. The npm tree does not change.

* ci(pnpm): install with pnpm in the remaining workflows

The follow-up to switching CI and release: every other workflow that
installs the root workspace now uses the pinned pnpm with
--frozen-lockfile, so no job tests a graph that release does not ship.

- e2e, tui-parity, repo-hygiene, web-shell-visuals, serve-ab, sdk-java,
  windows-runner-smoke, qwen-autofix, the SDK releases, finalize-release,
  sync-release-to-oss, the root install in desktop-release and
  cd-cua-driver, and the sandbox Dockerfile.
- setup-node drops the root npm cache (package-manager-cache: false); hosted
  jobs restore the pnpm store through the shared composite action, and
  qwen-autofix keeps af-012's rule that the persistent pool restores no
  remote cache.
- serve-ab and web-shell-visuals install a separate base checkout that can
  predate pnpm-lock.yaml, so those installs fall back to npm ci there.
- Out of scope, unchanged: packages with their own lockfile (cua-driver's
  TypeScript SDK, desktop-shell, live-host, mobile-mcp), and the triage
  lanes with npm-cache.yml and the npm audit gate, which move with the
  package-lock.json retirement.
- New size baselines for the touched workflows; qwen-autofix stays under
  the 470000-byte gate.

* fix(pnpm): link the workspace packages that root-level code imports

npm links every workspace package into the root node_modules, so
integration-tests/ and scripts/, which are not workspace packages
themselves, can import @qwen-code/* by name. pnpm links a workspace
package only where it is a declared dependency, and the root declared
none, so the no-AK integration gate could not find
@qwen-code/qwen-code-core.

The root now declares the seven packages that root-level code imports at
runtime as file: devDependencies: acp-bridge, channel-base, the CLI,
core, sdk, web-shell and web-templates. npm already links them, so its
tree does not change (letting npm re-resolve the lock reproduces the
same root entry); .pnpmfile.mjs rewrites them to workspace:*, which pnpm
links at the root.

* ci(pnpm): move triage, the store producer and the root audit to pnpm

- qwen-triage's verify and tmux-testing lanes install the PR with
  `corepack pnpm install --frozen-lockfile` from a restored pnpm store.
  The one retry clears node_modules first, because pnpm install, unlike
  npm ci, keeps it.
- npm-cache.yml becomes pnpm-store.yml, keyed on pnpm-lock.yaml.
- security-checks audits the root workspace with `pnpm audit`, which reads
  the lockfile, so the root install step goes. The vendored lockfiles keep
  npm, and the retry recognises pnpm's endpoint error too.
- qwen-autofix drops the hosted-only store cache: its heavy jobs forbid
  local `uses: './...'` actions, and the file sits at the size gate.
- cd-cua-driver: setup-node@v4 has no package-manager-cache input.
- package-assets fixtures install sharp rather than listing it in
  package-lock.json, matching how prepare-package now resolves it.
- AGENTS.md, CONTRIBUTING.md and the npm workspace guide install with pnpm.

* feat(review): install pnpm repos in qwen review's build/test

qwen review installed dependencies only for a repo with package-lock.json
and reported a pnpm repo as unsupported, so reviews of qwen-code would
lose their build and test once its npm lockfile goes.

- lockfileInstaller() picks npm or pnpm from the committed lockfile. With
  both, `packageManager: pnpm@...` chooses pnpm; anything else keeps npm.
- A pnpm repo installs with `corepack pnpm install --frozen-lockfile`
  through the same disk, budget and sandbox path as `npm ci`, and counts
  as installed once node_modules/.modules.yaml exists.
- Scripts still run through `npm run`, and the npm path is unchanged.

* ci(pnpm): retire the root package-lock.json

pnpm-lock.yaml is now the only root lockfile.

- check-lockfile keeps the pnpm integrity check and Playwright parity,
  which now reads pnpm importers and snapshots. The npm-vs-pnpm agreement
  and build-approval checks go: pnpm 11's strictDepBuilds and every CI
  install's --frozen-lockfile enforce what they covered.
- pnpm-lock-freshness.yml goes; it regenerated pnpm-lock.yaml from
  package-lock.json.
- Release: version.js drops its package-lock refresh, the release commit
  stages pnpm-lock.yaml and the SDK release stages no lockfile, so neither
  `git add` fails on the missing file. `npm version` in the SDK and
  mobile-mcp releases passes --no-workspaces-update so it cannot reinstall
  the root with npm.
- cd-mobile-mcp installs from the root pnpm lockfile. setup-node in
  cd-mobile-mcp, qwen-code-pr-review and desktop-release no longer keys an
  npm cache on the root lockfile.
- check-desktop-isolation reads pnpm importers; build.js, build_sandbox.js
  and preflight install with pnpm.
- pnpm-lock.yaml, pnpm-workspace.yaml and .pnpmfile.mjs join the autofix
  supply-chain class and repo-hygiene's deny list; pnpm-lock.yaml joins
  autofix's generated-diff exclusions, sdk-java's path filter and the
  platform-sensitivity manifests.
- .gitignore lists /package-lock.json so an npm install cannot commit it
  back. Tests and docs follow.

* fix(standalone): read the node-pty versions from the pnpm lockfile

* chore(vscode-ide-companion): regenerate NOTICES.txt after the lockfile swap

* chore(vscode-ide-companion): regenerate NOTICES.txt from the pnpm dependency tree

The committed NOTICES.txt was generated from an npm install and does not
match the tree produced by the pnpm install the release pipeline now uses
(659 resolved dependencies, differing @types/node and mime-db versions).
Regenerating with the pinned pnpm install makes the freshness check
reproducible.

* fix(ci): drop the npm cache from the daily dependency audit

actions/setup-node's npm cache keys off the root package-lock.json, which this branch retires, so the scheduled job would fail at Set up Node before the audit ran. No pull_request run reaches this workflow to catch it. The root tree is never installed here either: pnpm audit reads pnpm-lock.yaml directly.

Matches the sibling workflows that already set package-manager-cache: false; scripts/tests/security-workflows.test.js now pins it.

* chore(ci): stop banking main-side drift in the workflow size ratchet

The ratchet exists to turn a workflow's growth into one reviewed line. This branch re-recorded entries for 17 workflows it never touches, several upward by 0.4-2.8 KB - drift that happened on main, banked into the manifest without review.

Restore those to their merge-base values, so .size-baseline changes only for the 22 workflows this branch actually edits. The entries were not needed to go green: ci.yml passes the PR's base commit, so a file that drifted on main and is otherwise untouched earns a warning, not a failure.

* fix(release): initialize standalone state before main

* test(browser-use): read Playwright pin from pnpm lock

* fix(ci): address pnpm migration review blockers

* fix(desktop): stop reading the retired root lockfile

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-09-20 02:11:25 +00:00
qqqys fa2be32393 feat(goal): judge terminal proposals from the transcript tail (#12105)
* feat(goal): judge terminal proposals from the transcript tail

The verifier used to receive the records the model cited by uuid from a
bounded catalog. A turn of a hundred tool calls overflowed the catalog,
update_goal refused the proposal, and the checkpoint side query that had
to compress the overflow failed on its own output or spent a stall on a
full claim list (#12053).

The verifier now reads the tail of the Goal's transcript by itself: the
records after the evidence cursor that belong to this Goal revision,
newest first, until the next one no longer fits what the 256 000-byte
request has left after its measured envelope. One record contributes at
most 8 000 bytes, cut in the middle. There are no reserved slots, quotas
or coverage rules. The request lists the turns present and how many older
records were left out, and the verifier rejects when the proof may sit
among them.

update_goal takes a status and a reason; evidenceRefs is accepted and
ignored. get_goal returns no catalog. No checkpoint is armed and a pending
one from an earlier build is dropped on restore. The verifier gets a fixed
120 s, and a verifier that gives no verdict pauses the Goal instead of
stopping it as usage_limited.

* perf(goal): count the records behind the verifier window without rendering them

* test(goal): restore drops a checkpoint an earlier build left pending

* docs(goal): realign the settings table

* fix(goal): pause for a shorter objective when the request cannot hold one full record

Review of #12105: with the guard at zero, a budget too small for one
capped record left the verifier a stub or nothing, so it could only reject
for what the window left out and the model could only propose again until
a budget stopped the Goal. Pause with the reason that names /goal edit as
soon as the room left is under one record at the content limit with its
bytes doubled by JSON escaping.

Also note in the telemetry reference that the two evidence limit kinds are
no longer produced.
2026-09-17 15:58:21 +00:00
qqqysandqqqys e18fa2ce7b feat(telemetry): emit a Goal lifecycle event and metrics (#11927)
* feat(telemetry): emit a Goal lifecycle event and metrics

A Goal runs on its own, and every way it stops is recorded only in the
session transcript: telemetry had no Goal event and no Goal metric.

Each committed transition an operator acts on (the user's controls and
the stops: complete, blocked, usage_limited, verifier_reject) is now a
`qwen-code.goal_state` event, subscribed where the Config builds the
runtime. Per-turn causes are left out. The event carries identifiers,
enums and numbers only; the objective, stop reasons and checkpoint
failure text are never sent, because `telemetry.logPrompts` defaults to
on. A transition counter and two outcome histograms keep the Goal id off
metric attributes.

`restore()` republishes a resumed session's Goal with the cause of the
record it recovered, which a subscriber cannot tell from the live
transition that first produced it. Broadcast listeners now receive an
optional third argument, and the restore broadcast is the only one that
marks itself `replayed`, so a resumed session does not report its Goal
a second time.

* fix(telemetry): correct Goal reporting scope and histogram buckets

* test(telemetry): handle optional Goal recovery accessor

* fix(telemetry): pin Goal metric semantics and coverage

---------

Co-authored-by: qqqys <[email protected]>
2026-09-16 03:26:08 +00:00
qqqys 9f8cb17ca6 feat(workflows): size guideline, structure preview and large-run warning (#11939)
* feat(workflows): size guideline, structure preview and large-run warning

A Dynamic Workflow Size setting (small, medium by default, large or
unrestricted) adds a guideline paragraph to the Workflow tool description,
and a change made mid-session reaches the model as a one-shot reminder on
the next prompt, because the description is built once at startup.

The approval dialog gains a Structure section that reads the script
without running it and lists its runs of top-level calls, its
parallel()/pipeline() fan-outs and its loops, with the first prompts of
each. No agent estimate is shown: a fan-out over args has no static count.

A running workflow that schedules more agents than the guideline (25 when
unrestricted) or projects past 1.5M output tokens is flagged once, without
being stopped: the background-tasks pill, the detail view, the run log,
the snapshot, the daemon/ACP task status and one telemetry event carry it.
Journal replays do not count as scheduled agents.

A script that calls Math.random(), Date.now(), new Date(), Date.parse() or
Date.UTC() is refused at launch, before any agent is dispatched, instead of
failing on whichever call reaches the sandbox guard first.

Part of #11013

* fix(core): keep workflow structure rows from reading as agent counts

- Drop the `× N` multiplier from Structure rows. The number was a count of
  `agent()` call sites, so a data-driven fan-out read as one agent. Rows now
  say "N agent() call sites" only when there are several, and the heading
  states that a loop or fan-out runs each call many times.
- Give a `parallel()`/`pipeline()` over functions built before the call its
  own row, so the fan-out is not presented as a sequential step only. The
  calls inside those functions stay on the row where they are written; the
  file overview records that no-dataflow limit.
- Refuse the bare `Date()` call at launch, which the sandbox already throws
  on, and name it in the authoring skill.
- Show the structure for scripts loaded by `scriptPath` or `name` too, now
  that the dialog reads their content.
2026-09-15 14:16:25 +00:00
易良andQwen-Coder 908452bbd5 fix(telemetry): gate request_text/response_text on logPrompts (#11670)
* fix(telemetry): gate request_text/response_text on logPrompts

When telemetry.logPrompts is false, skip serializing API request and
response content into telemetry instead of emitting it to every sink.
Also add request_text to the log-to-span bridge's sensitive-attribute
denylist so traces-only telemetry cannot diverge from the native logger.

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

* fix(core): add streaming logPrompts coverage and format logging tests

Prettier-formats the two new generateContent logPrompts tests so the
Lint & Static gate passes, and adds two streaming counterparts driving
generateContentStream so the request_text/response_text stripping
invariant is pinned on the path that carries interactive traffic.

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

* docs: enumerate request_text in sensitive span attribute docs

Adding request_text to SENSITIVE_ATTRIBUTE_KEYS makes the retained set on
log-to-span bridge spans larger than what two doc statements enumerate.
Update both enumerations to four fields, carry the log_prompts_enabled
condition into the api_request/api_response attribute reference, and widen
the logPrompts descriptions to cover API request/response text.

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

* docs(telemetry): correct request_text/response_text gating wording

The prior "excluded if log_prompts_enabled is false" phrasing over-promised
presence for response_text (it is also absent for internal prompt ids and for
thought-only/tool-call-only turns) and misdescribed request_text (the key ships
empty on native OTLP log export, and is only absent from telemetry.outfile and
log-to-span bridge spans). Qualify the includeSensitiveSpanAttributes bridge-span
list so prompt / request_text / response_text note they additionally require
telemetry.logPrompts.

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

* docs+test(telemetry): resolve request_text/response_text sink-contract findings

Address four review findings on the logPrompts gating of request_text/response_text:

- R1-1/R2-1: rewrite the api_request/api_response attribute docs so the three suppression causes (internal prompt id, no visible text, logPrompts off) all collapse to "key present but empty on native OTLP log export, absent from telemetry.outfile and log-to-span bridge spans", and note that bridge spans additionally require includeSensitiveSpanAttributes for both keys.
- R2-2: record that opaque thoughtSignature provider payload is exported verbatim with request_text, with the policy decision tracked in #11682.
- R3-1: add loggers.test.ts assertions that request_text/response_text are present-but-undefined keys when logPrompts is off, and a FileLogExporter test that undefined-valued keys are dropped from the outfile JSON.

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

* style(core): format loggers.test.ts

* test+docs(telemetry): pin logPrompts presence matrix and correct bridge gating docs

- loggers.test.ts: drop the inert getTelemetryLogPromptsEnabled overrides; the
  two present-but-empty tests now name and pin the native OTLP log export leg
  (key present with an empty value) instead of a flag neither function reads.
- file-exporters.test.ts: drive FileLogExporter.export() with a real
  ReadableLogRecord rather than poking the inherited private serialize.
- telemetry.md: log-to-span bridge spans keep function_args under
  includeSensitiveSpanAttributes alone, plus prompt/request_text/response_text
  only when logPrompts is also enabled.

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

* test(telemetry): avoid pinning exporter empty-key internals

* docs(telemetry): list error attributes retained by the bridge

* test(logging): pin opaque thoughtSignature survival in request_text

Add a regression test asserting the thoughtSignature provider payload
survives toContents serialization into request_text.

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

* Revert "test(logging): pin opaque thoughtSignature survival in request_text"

R5-1 is declined and deferred to #11682, which owns the enabled-policy
thoughtSignature decision. This PR only changes the logPrompts=false
privacy gate; a test that freezes the opposite policy widens scope.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-09-12 11:26:19 +00:00
ChiGao e09c461a92 fix(cli): restore OpenTUI banner, footer, and loading states; fix the OSS mirror flavor (#11437)
* fix(cli): restore banner, footer, and loading states in the OpenTUI shell

The batched OpenTUI migration restructured the original single-file backend
into modules and dropped three user-visible pieces of the chrome along the
way: the startup banner, the bottom status line, and the responding
spinner. The OpenTUI renderer therefore booted into a screen that looked
nothing like the ink one it was meant to replace.

Rebuild all three as their own modules and mount them in the app shell,
following the ink layout: banner at the top, loading indicator above the
composer, status line at the bottom.

Two defects carried over from the original implementation are fixed rather
than copied. The context-usage label was missing its percent sign, and the
approval-mode colour check compared against a label that never matched, so
the auto-edit mode fell through to the default colour.

The context-file count is intentionally not restored: the shell no longer
tracks it and the current ink footer dropped it too.

* fix(cli): tick the OpenTUI spinner at the rate ink uses

The restored spinner ran at 120ms, carried over from the pre-batch
implementation, while the ink responding spinner it mirrors runs the same
frame set at 80ms and the existing OpenTUI compaction spinner does too. The
same animation therefore looked visibly slower under one renderer.

Name the interval and set it to the shared rate. The frame set itself is
unchanged.

* fix(release): sync the OSS mirror by what the tag shipped, not by a variable

The workflow that mirrors a release to Aliyun OSS decided whether to expect
the bun/OpenTUI preview archives from a repository variable. A repository
variable describes the default branch, while the checkout and the downloaded
assets come from the tag being synced, and the workflow can be re-dispatched
for any tag. Re-syncing a tag cut before the preview flavor existed therefore
asked that tag's own verify script for archives the release never shipped:
v0.22.3 fails because its script does not know the flag at all, and v0.23.0
fails on a missing checksum entry.

Derive the flag once from the archives actually downloaded and let all three
consumers read it, so the mirror follows the release instead of the branch.

Two related gaps in the same gate are closed here. The test covering it
asserted only the variable name, so inverting the comparison and silently
changing the flavor's default polarity stayed green; it now pins the
operator. And the variable appeared in no documentation, so an operator had
no way to learn that leaving it unset is what keeps the flavor off.

* fix(cli): source the OpenTUI chrome from main's shared helpers

The restored banner, footer, and responding indicator had grown private
copies of mappings main already owns, so the two renderers could drift.
Route the approval-mode label through formatApprovalModeName, extract the
context-usage format and version-label rules into ink-free helpers both
trees consume, and take the spinner frames and loading phrases from the
shared constants and cycler.

Also truncate the footer rows and the indicator phrase to the terminal
width so a long branch, path, or phrase cannot wrap the chrome mid-turn,
and read typed Config accessors in the banner instead of structural
casts and a manual package.json read.
2026-09-09 09:26:07 +00:00
c069801657 feat(workflows): journal failed agents and settle agent failures to null (#11196)
* feat(workflows): journal failed agents and settle agent failures to null

A workflow's journal recorded that an agent started and that it returned,
and nothing in between. A key with a `started` and no `result` could be an
agent that failed or a run that was interrupted with that agent in flight,
and a resume had no way to tell the two apart — it re-ran both and said
nothing about either.

The same gap ran through the script contract. A `parallel()` slot whose
agent hit its turn cap became `null`, while the identical failure in a bare
`await agent()` threw and ended the run, so the shape of the call decided
whether one broken agent cost a slot or the whole workflow.

Agent-level failures — turn cap, time cap, model error, no structured
result, the user stopping that agent — now settle to `null` in both forms
and are journaled as `failed`. Run-level conditions still throw: the token
budget, the agent cap, a stall that survived every attempt, a cancelled run.
A cancelled run writes no `failed` records at all, so its open keys read as
interrupted rather than broken.

What failed is now reported rather than counted: the run trailer and the
background completion notification list each failed agent with its error,
capped with a named remainder, and a resume says which calls it is re-running
and why. The run event carries agents_failed, agents_cached and
agents_respawned.

Part of #11013

* fix(workflows): only report a respawn for a call that never finished

The respawn report fired for every call that ran live on a resume, not
only for the ones the journal had left unfinished. Once any call misses,
the prefix invariant sends every later call live too — including calls
that completed last time — so an ordinary interrupted fan-out reported
its already-finished agents as interrupted alongside the one that really
was, and counted them.

A key with a journaled result is now excluded: it is re-running because
of what happened upstream of it, which is a fact about the run rather
than about that agent.

Part of #11013

* fix(workflows): align failure settlement and resume reporting

* fix(workflow): preserve run failure semantics

* fix(workflows): journal starts only after dispatch admission

---------

Co-authored-by: qqqys <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
2026-09-09 01:17:45 +00:00
DragonandShaojin Wen 53e3fc736e feat(core): send session ID to Routify endpoints (#10896)
* feat(core): add Routify session affinity header

* fix(core): address session header review feedback

* test(core): verify Anthropic session fetch

* fix(core): align Gemini session destination

* test(core): cover request-level Gemini HTTP options

---------

Co-authored-by: Shaojin Wen <[email protected]>
2026-09-05 04:20:15 +00:00
易良andQwen-Coder 7df5ac6898 fix(ci): isolate and bound release validation workloads (#10765)
* fix(ci): keep release validation off contended ECS hosts

Pin every release validation job to the hk4 host label, move the nightly
and preview crons out of the pool's peak hours and apart from each other,
give the web-shell Vitest project the same shared-ECS timeout ceiling as
cli/core, and retry timing-only workspace test failures twice.

Claude-Session: https://claude.ai/code/session_01AWWgJEqafyAT1Mc75T8N7h

* fix(ci): harden release validation isolation

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

* fix(ci): serialize release validation runs

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

* fix(ci): preserve manual release dispatches

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-09-02 04:58:08 +00:00
ChiGaoand秦奇 d96f264de7 feat(telemetry): link daemon HTTP request spans to inbound W3C traceparent (#9391)
* feat(telemetry): link daemon HTTP request spans to inbound W3C traceparent

The daemon HTTP surface records a request span per request, but every span
starts a new trace: a caller forwarding the standard W3C traceparent header
(OTel-instrumented clients, proxies, gateways) gets no linkage back to its
own trace.

Extract traceparent/tracestate from inbound request headers in the daemon
telemetry middleware and parent the request span to that remote context.
Extraction reuses the same path as the existing JSON-RPC _meta extraction
(global propagator first, strict manual fallback so behavior is identical
without a registered SDK) and fails closed: requests without a valid header
keep the exact current span shape.

* fix(telemetry): guard inbound traceparent sampling and align W3C fallback

- Force TraceFlags.SAMPLED on inbound HTTP parents via the existing
  shouldForceSampled() matrix: an unsampled remote parent under the
  default parentbased_always_on sampler silently dropped the request
  span, the whole next() subtree, and the session-subprocess spans
  forwarded via _meta (review C1).
- Replace the hand-rolled manual fallback parser with a direct
  W3CTraceContextPropagator instance so acceptance rules (future
  versions, tracestate, all-zero ids, version-00 extension field)
  match the registered path with or without an initialized SDK.
- Gate middleware extraction behind isTelemetrySdkInitialized() to
  skip the hot-path parse when telemetry is off, and emit a debug
  daemon log when a present-but-invalid traceparent header is
  rejected.
- Re-export DaemonRequestSpanOptions from the core barrel and add a
  type-level guard so the parentContext field cannot silently
  disappear (vitest alone cannot catch its removal).

* chore(vscode): regenerate companion NOTICES.txt for @opentelemetry/core

* fix(telemetry): lazy-load OTel core fallback propagator behind SDK init

Address review feedback on the inbound traceparent linkage:

- Keep @opentelemetry/core out of the static graph. The module-level
  W3CTraceContextPropagator in daemon-tracing.ts pulled the CJS barrel
  (bot-measured +65,046 bytes) into every closure loading that module,
  including telemetry-off deployments. daemon-tracing.ts now keeps only a
  holder + setter (setDaemonFallbackPropagator, typed against
  @opentelemetry/api — type imports stay free at runtime); the lazy
  sdk-impl.ts chunk, whose closure already contains @opentelemetry/core via
  sdk-node/resources, constructs and injects the W3C instance on the
  successful SDK assembly path. Until injection, extraction returns no
  parent context: the HTTP edge is already gated on
  isTelemetrySdkInitialized (nothing changes when telemetry is off), and
  the _meta edge's consumers (withDaemonSpan / withInteractionSpan)
  short-circuit on the same flag, so an unresolved pre-init parent never
  had an observable effect.
- Add the mutation-verified fail-closed test for the header-extraction
  try/catch in daemonTelemetryMiddleware: a throwing extractor leaves the
  request settling normally (recordDaemonHttpRequest still fires once)
  with no parentContext on the span options.
- Record the rejected traceparent value (truncated to 128 chars) as
  http.request.header.traceparent on the invalid-header breadcrumb —
  traceparent only carries trace-id/span-id/flags, so this is
  privacy-safe and makes broken cross-service joins diagnosable.

Also document why the _meta extraction path deliberately skips
shouldForceSampled (trusted in-process bridge vs external HTTP input).

* feat(telemetry): carry inbound trace id into daemon access log with telemetry off

Telemetry off (the default) left daemon logs without any trace id: with no
request span, the log trace prefix never fires, so a caller forwarding W3C
traceparent could not be joined to its daemon log lines.

The middleware now parses the header with a plain regex
(extractInboundTraceId — same shape/all-zero/ff rejections as the W3C
propagator, no OTel machinery) and stores the trace id on the per-response
telemetry context. The access log emits it as the camelCase traceId field
of "request completed", keeping the log-based join alive with no telemetry
config and no trace backend. With telemetry on nothing changes: the request
span already carries the caller's trace id into the log prefix.

* fix(telemetry): unify _meta/HTTP sampling and repair build export

- Export extractInboundTraceId from the core barrel: the previous commit
  exported it from daemon-tracing.ts only, so downstream package builds
  failed with TS2305.
- extractDaemonTraceContext now applies the same shouldForceSampled()
  matrix as the HTTP edge: the _meta path is also reachable from direct
  ACP clients (acpAgent newSession/loadSession/unstable_resumeSession
  and Session.prompt pass caller-controlled _meta), so an external
  sampled=0 parent no longer silences daemon spans there either. The
  in-process bridge is unaffected (its injected values are already
  SAMPLED).
- The rejected-header breadcrumb now goes through sanitizeLogText so a
  crafted traceparent cannot forge log line structure with control
  characters.
- Add the sdk-impl wiring test: after initializeTelemetry the injected
  W3C fallback propagator resolves inbound HTTP parents.

* fix(telemetry): align log-path traceparent parsing and emit traceId in both modes

- extractInboundTraceId now mirrors the vendored W3C propagator's
  acceptance exactly: single optional leading/trailing whitespace and
  trailing extension fields above version 00 (version 00 must stay
  four fields). Previously the strict four-field anchor made the two
  paths disagree on the same forward-compatible header, silently
  dropping the access-log traceId for exactly the callers the
  propagator path supports.
- The camelCase traceId access-log field is now captured whenever a
  valid header parses, regardless of telemetry mode, so one saved log
  query / alert shape works for every deployment; with telemetry on the
  snake_case span prefix carries the same id redundantly.

* fix(telemetry): move inbound trace id getter out of the middleware module

52d572c0f2 made the access log statically import the telemetry
middleware module to read the captured inbound trace id. The access log
sits inside the serve fast-path pre-listen closure (run-qwen-serve
imports it directly), so the middleware's core-barrel import graph came
along for the ride and check-serve-fast-path-bundle started failing:
the 5.6MB core chunk (shell tool, glob, chokidar, @iarna/toml, fzf)
became statically reachable from run-qwen-serve.

Move the response-context symbol, its type, and the
getDaemonTelemetryInboundTraceId getter into a new import-light
telemetry-context.ts; the middleware imports the symbol from there and
re-exports the getter, so the access log no longer links against the
telemetry module at all.

* fix(telemetry): capture inbound trace id pre-auth under a dedicated symbol

* test(telemetry): pin the trace id seam through the context module getter

---------

Co-authored-by: 秦奇 <[email protected]>
2026-08-19 12:21:49 +00:00
jinyeandQwen-Coder d670bb8109 feat(telemetry): Trace main agent invocations (#9107)
* feat(telemetry): Trace main agent invocations

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

* codex: address PR review feedback (#9107)

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-08-14 02:50:22 +00:00
de470b95b4 feat(telemetry): align session lifecycle with OpenTelemetry (#8616)
* fix(telemetry): align session lifecycle with OTel

* feat(telemetry): complete session lifecycle coverage

* fix(telemetry): deduplicate deferred session starts

* test(telemetry): cover duplicate session starts

* fix(serve): emit daemon session starts

* fix(telemetry): repair session lifecycle test wiring and record attributes (#8616)

Restore the missing logSessionEnd export in the config-session-env mock
(the Test-check failure), add event.timestamp to the session lifecycle
records to match every sibling emitter, and pin the previously untested
wiring: end-before-start ordering and the /clear non-continuation rule at
the Config level, the deferred-init catch-up guard, the shutdown emission,
and the loggers->session-events link. Correct the telemetry docs claims
about session.previous_id, end_session, and the log event catalog.

* fix(telemetry): skip session lifecycle transition on same-id resume (#8616)

* test(telemetry): pin session lifecycle behaviors per review (#8616)

Mutation testing in review round 4 showed three properties survived the
whole suite unguarded: the session-start guard resetting on session.end,
the daemon runtime config's isTelemetryInitializationDeferred flag, and
the catch-up session.start emitting only after NodeSDK.start(). Add
focused assertions for each, and replace a duplicated config mock literal
with the makeFakeConfig factory.

* fix(telemetry): emit session start catch-up on every init path (#8616)

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

---------

Co-authored-by: zjunothing <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-08-12 02:53:06 +00:00
+1 d1648b3af9 feat(telemetry): Track tool execution outcomes (#8180)
* feat(telemetry): track tool execution outcomes

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

* fix(telemetry): address execution-status review feedback (#8180)

- Update stale nonInteractiveToolExecutor expectations for executionStatus (red CI)
- Scope the cancelled span-status short-circuit to tool_call events so other
  cancelled events carrying an error keep ERROR status
- Record loop-detection skips as UNKNOWN, not EXECUTION_DENIED, keeping the
  denial metric accurate
- Assert execution_status on the resolved-with-error PostToolBatch path
- Raise tool-call observer-failure logging from warn to error
- Clarify subagent-projection exclusion and JSONL compatibility in design doc

* fix(core): address review findings 3-6 on tool execution status (#8180)

- recordToolExecutionMetrics now merges common attributes (session.id
  opt-in) like every other counter in metrics.ts
- Lift TOOL_FAILURE_KIND_ATTRIBUTE / TOOL_FAILURE_KIND_CANCELLED into
  telemetry/constants.ts so coreToolScheduler and session-tracing share
  one definition
- Add debugLogger to runToolTelemetrySink catch (was silent)
- Replace delete-based absence in withPostToolBatchStop with conditional
  spread

* fix(core): address remaining review findings on tool execution status (#8180)

- Pass the frozen executionStatus variable instead of the literal
  'success' in the post-hook-stop error response, keeping the frozen
  value the single source of truth (finding 4)
- Force-finalize the deferred PostToolBatch parent span in the abort
  drain, since that terminal path cancels the batch hook that otherwise
  owns the span; documents the invariant at the call site (finding 6)
- Comment the loop-detection guard so the permission-cancellation
  exclusion from invalid-param loop detection is explicit (finding 9)
- Rename the design doc to the dated docs/design convention and note
  the schedule()/handleConfirmationResponse() resolution contract
  change for embedders (finding 2, doc convention)

* docs(core): note schedule() resolution contract in tool execution status design (#8180)

Record the embedder-facing behavior change that schedule() and
handleConfirmationResponse() resolve with a terminal error call rather
than rejecting, so a failing tool no longer aborts its siblings.

* fix(telemetry): address review feedback for tool execution status (#8180)

- Document the new tool_call attributes (call_id, execution_status), the
  qwen-code.tool.execution.count metric, the tool.execution span attributes,
  and the tool.failure_kind=cancelled span field in telemetry.md.
- Pass ToolErrorType explicitly at loop-detection skip sites instead of
  inferring it from the skip message string, so copy edits cannot silently
  reclassify loop skips as approval denials.
- Simplify withPostToolBatchStop response construction (drop the
  destructure-and-reattach used to preserve a missing execution status).
- Add a debug breadcrumb when a PostToolBatch stop has no span to attach to,
  and a one-time warning when PostToolBatch hook detection fails open.
- Drop the try/catch wrapping the pure isTelemetrySdkInitialized getter.
- Clarify the design doc invalid-combination wording and note that the
  execution-failure SLI cannot be attributed to a specific tool.
- Add a regression test pinning that schedule() resolves (not rejects) when a
  tool execution throws.

* fix(telemetry): address round-7 review feedback for tool execution status (#8180)

* fix(telemetry): restore type-safety fallback for executionErrorType (#8180)

* fix(telemetry): align tool execution failure outcomes

Keep Core and ACP cancellation arbitration consistent, preserve structured post-processing errors, and restore QwenLogger MCP metadata privacy.

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

* fix(core): address review suggestions for tool execution status (#8180)

* test(core,cli): strengthen test-efficacy for tool execution status (#8180)

* fix(core): address review suggestions for tool execution status (#8180)

- Improve post-processing cancellation message to indicate the tool had
  already completed, preventing silent model redo of completed work
- Remove dead !isExecutionTimeout conjunct in Session.ts PostToolUse
  cancellation check (unreachable: timeout always sets toolResult.error)
- Replace construct-then-delete with destructuring in withPostToolBatchStop
- Move all failure-kind constants to telemetry/constants.ts so the full
  documented vocabulary lives in one place
- Re-export StructuredToolError from tool-error.ts instead of importing
  from the unrelated priorReadEnforcement module
- Add JSDoc to normalizeToolCallEvent documenting key-absent semantics
- Add ordering-safety comment to createParentAbortRace microtask guarantee
- Document endToolExecutionSpan not_started guard as defence-in-depth
- Document PostToolBatch span leak window in finalizeToolSpan
- Add design doc note about hand-placed cancellation check invariant
- Add test for unknown execution_status normalization path
- Revert unrelated generate-notices.js formatting change

* fix(core): address review feedback for tool execution status (#8180)

- Gate cancel message on executionThrew so the model sees 'User
  cancelled tool execution.' when execute() rejected under abort,
  reserving 'already completed' wording for post-processing cancels
- Move StructuredToolError into tool-error.ts to break the
  tool-error ↔ priorReadEnforcement module cycle
- Revert unrelated Prettier reformat in generate-notices.js

* test(core): pin both tool cancellation notices; extract them as constants

afd349ca gated the cancel message on executionThrew but left the two
wordings as bare literals at four sites and added no test. That is the
exact shape the bug had: it was introduced by editing one literal and
missing the others.

Extract TOOL_CANCELLED_{BEFORE,AFTER}_COMPLETION_MESSAGE so the four
sites cannot drift, and add regression tests for both paths — a tool
interrupted mid-flight (execute() rejected under abort) must report
"User cancelled tool execution.", while a cancel after execute()
returned must report that the output was discarded. The mid-flight test
fails against the pre-afd349ca behaviour.

Co-Authored-By: Claude Opus 5 <[email protected]>

* fix(core): keep MCP reconnect for a timeout on a dead transport

Classifying every `-32001` as EXECUTION_TIMEOUT skips
handleReconnectOnError, which previously recovered one real case: the
transport dies mid-request, the SDK request times out because no
response will ever arrive, and the server is already recorded
DISCONNECTED. That reconnected and retried; now it hard-fails and the
user has to retry by hand.

Divert back to the reconnect path only on positive evidence the
transport is dead. Note that getMCPServerStatus() reports DISCONNECTED
for servers it has never seen, so the guard checks for a *recorded*
DISCONNECTED — the naive comparison misroutes every timeout from a
server whose status was never registered, which broke four existing
timeout tests when tried.

A timeout on a healthy server is still EXECUTION_TIMEOUT: retrying it
after a reconnect would just double the wait. The client-side idle
timeout keeps classifying unconditionally; it is our own timer, not a
transport signal.

Co-Authored-By: Claude Opus 5 <[email protected]>

* fix(core): address blocking review feedback for tool execution status (#8180)

Two blocking items from the maintainer review:

1. Post-processing cancellations dropped persistedOutputFiles (and
   visionBridgeNotice) along with the model-visible output, orphaning
   files the tool had already spilled to disk. createCancelledResponse
   now carries both, and every cancelAfterPostProcessing site passes
   what it has; the settle-then-abort and hook-stop paths do the same.

2. A -32001 that lands while the parent signal is aborted is the SDK's
   abort rejection or a timeout that raced with a cancel; classifying
   it EXECUTION_TIMEOUT would count user cancels against the timeout
   SLI. isExecutionTimeoutFailure now defers to the abort in both
   catch blocks, regardless of which side settled the race first. The
   two tests that pinned the opposite timeout-wins ordering are
   updated to the abort-wins semantics the review asked for.

Co-Authored-By: Qwen Code <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: qwen-code-bot <[email protected]>
Co-authored-by: qwen-code-ci-bot <[email protected]>
Co-authored-by: Qwen Code Autofix <[email protected]>
Co-authored-by: qwen-code-ci-bot <[email protected]>
Co-authored-by: Claude Opus 5 <[email protected]>
Co-authored-by: Qwen Code <[email protected]>
2026-08-03 10:21:44 +00:00
Shaojin Wenandverify 72bd3dccc2 ci: remove broken legacy scheduled PR triage workflow (#8434)
The Gemini-era scheduled PR triage workflow has been dead weight for a
long time:

- Its only business value — syncing labels from the linked issue to the
  PR — never fires: gh exports closingIssuesReferences as a flat array,
  so the script's '.closingIssuesReferences.nodes[0].number' jq path
  always errors, the error is swallowed by 2>/dev/null, and every PR
  falls into the "No linked issue found" branch. The latest production
  run logged 157 "No linked issue" hits and zero label syncs, despite
  many of those PRs having linked issues.
- LABELS_TO_REMOVE is computed but never applied, PRS_NEEDING_COMMENT is
  never appended to, and the prs_needing_comment job output has no
  consumer — the rest of the script is dead code.
- It burns 1+N API calls against every open PR every 15 minutes.
- The id-token: write permission is a leftover from the Gemini/GCP OIDC
  era; nothing in the bash script uses it.

Real PR triage lives in qwen-triage.yml. Remove the workflow and its
script, drop the stale docs section describing behavior it never had,
and pin the file into the legacy-workflow regression list.

Co-authored-by: verify <verify@local>
2026-08-03 10:20:42 +00:00
95657feb6e feat(core): add GenAI time-to-first-chunk tracing (#8150)
* feat(core): add GenAI time-to-first-chunk tracing

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

* fix(core): address review feedback on GenAI streaming telemetry (#8150)

* fix(core): correct ttft_ms migration claim in telemetry docs (#8150)

* fix(core): address review feedback on GenAI streaming telemetry (#8150)

* fix(core): address review feedback on GenAI streaming telemetry (#8150)

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Qwen Code Bot <[email protected]>
Co-authored-by: qwen-code-ci-bot <[email protected]>
2026-07-31 13:46:46 +00:00
jinyeandQwen-Coder 08798b4832 feat(core): Normalize tool-call terminal telemetry (#8176)
* feat(core): normalize tool-call terminal telemetry

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

* fix(desktop): handle cancelled tool telemetry replay

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-07-31 11:51:14 +00:00
jinyeandQwen-Coder 788e5cd3a8 feat(core): add ARMS session user ID (#7921)
Co-authored-by: Qwen-Coder <[email protected]>
2026-07-28 11:10:38 +00:00
jinyeandQwen-Coder 055523afab feat(core): Align GenAI content telemetry fields (#7667)
* feat(core): align GenAI content telemetry fields

Capture provider-final GenAI messages and tool payloads with the shared ARMS/OpenTelemetry field contract, and retire equivalent private content aliases.

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

* fix(telemetry): address content observability review

Keep ACP tool telemetry best-effort, improve diagnostics and sensitive-off coverage, and document finish-reason and tool-description behavior.

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

* docs(telemetry): clarify optional tool parameters

Document that invalid optional parameter schemas are omitted while required tool identities remain ordered and complete.

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

* docs(core): clarify telemetry compatibility behavior

Document the changed semantics of deprecated helpers and the reason the fallback context shadows key operations.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-07-24 18:42:48 +00:00
jinyeandQwen-Coder fcd358ae45 feat(core): align GenAI request telemetry fields (#7635)
Co-authored-by: Qwen-Coder <[email protected]>
2026-07-24 04:34:00 +00:00
3617397e5f feat(core): Align GenAI telemetry with ARMS (#7536)
* feat(core): align GenAI telemetry with ARMS

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

* fix(core): remove estimated token usage splits

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

* fix(core): address GenAI telemetry review feedback

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
2026-07-23 07:35:46 +00:00
4e21f82c28 fix(ci): consolidate issue triage ownership (#7180)
* fix(ci): consolidate issue triage ownership

Resolves #4786

* chore(vscode-ide-companion): regenerate NOTICES.txt to fix CI drift

* fix(ci): close triage ownership review gaps

* fix(ci): restore need-info label cleanup

* docs(ci): document issue retriage triggers

---------

Co-authored-by: Shaojin Wen <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
2026-07-19 10:58:40 +00:00
jinye 988a5021e8 docs(telemetry): comprehensive documentation update to match current implementation (#5960)
* docs(daemon): update developer docs for recent daemon PRs

- Add Last-Event-ID client reconnect guide (10-event-bus, 13-sdk-daemon-client)
- Add cross-connection vote routing section (04-permission-mediation)
- Add new capability tags: daemon_status, workspace_permissions, workspace_trust,
  workspace_github_setup, workspace_voice, workspace_voice_transcription,
  voice_transcribe (11-capabilities-versioning)
- Add new event types: trust_change_requested, github_setup_completed,
  extensions_changed, mid_turn_message_injected (09-event-schema)
- Fix _meta.serverTimestamp source description (09-event-schema, 10-event-bus)
- Fix async function* syntax in SDK example (13-sdk-daemon-client)
- Sync event/capability counts across all docs (43->47 events, 67->75 tags)

* docs(daemon): add workspace remember design doc (PR #5884)

Design document for the sessionless workspace remember API proposed in
PR #5884. Covers API endpoints, task lifecycle, implementation details,
events, error handling, and SDK integration.

Status: Proposed (not yet merged).

* docs(telemetry): comprehensive update to match current implementation

Added 34 undocumented events, 17 metrics, 11 daemon metrics, 2 spans. Fixed diff_stat attribute schema (was documented as JSON string, actually individual attributes). Added Performance Monitoring reserved section. Standardized attribute annotations with type and optionality markers.

* fix(telemetry): extract EVENT_TOOL_OUTPUT_TRUNCATED constant and add qwen-code prefix

Extracted hardcoded event name to constant for consistency with other telemetry events. Added standard qwen-code. namespace prefix. Updated test assertion to match the new prefixed event name.

* qwen: address PR review feedback (#5960)

* qwen: address PR review feedback (#5960)

* qwen: address PR review feedback (#5960)

* qwen: address PR review feedback (#5960)

* qwen: address PR review feedback (#5960)

* qwen: address PR review feedback (#5960)

* docs: address PR review feedback (#5960)

* docs: address PR review feedback (#5960)
2026-06-28 21:01:35 +00:00
07beac1ddb feat(telemetry): Make sensitive span attribute limit configurable (#5804)
* feat(telemetry): Make sensitive span attribute limit configurable

Default sensitive native OTel span attribute payload truncation to 1 MiB and allow users to override the limit via settings or environment.

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

* codex: fix CI failure on PR #5804

Add the new sensitive span attribute default export to telemetry/core mocks used by the full Windows test suite.

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

* codex: address PR review feedback (#5804)

Include invalid telemetry max-length values in errors, make the telemetry parser stricter, include the configured truncation limit in markers, and cover the exact truncation boundary.

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

* codex: address PR review feedback (#5804)

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

* codex: address PR review feedback (#5804)

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

* codex: address PR review feedback (#5804)

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

* test(cli): fix ACP worktree mock for telemetry limit

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

* fix(core): honor telemetry limit for model output spans

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

* test(core): cover telemetry span limit edge cases

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

* codex: address PR review feedback (#5804)

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

* codex: address PR review feedback (#5804)

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

* codex: address PR review feedback (#5804)

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

* codex: address PR review feedback (#5804)

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

* codex: address PR review feedback (#5804)

Keep sensitive span truncation results within the configured max length, make response-text extraction require an explicit cap, and cover whitespace-only sensitive span max length env values.

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

* codex: address PR review feedback (#5804)

Keep model-output span attribute writes best-effort and share visible response text extraction between log and sensitive span paths.

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

* fix(core): address telemetry review feedback

Bound prefixed tool span payloads, share sensitive max-length validation, and cover multi-part sensitive model output.

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

* fix(cli): update workspace facade core mock

Add telemetry sensitive span length constants to the qwen-code-core mock used by the workspace service facade test so settings schema imports can load.

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

* codex: address PR review feedback (#5804)

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

* codex: address PR review feedback (#5804)

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

* codex: address PR review feedback (#5804)

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
Co-authored-by: 易良 <[email protected]>
2026-06-25 08:08:16 +00:00
jinyeandQwen-Coder a234860a4a fix(core): Align MCP OAuth guidance and docs (#5589)
* docs: Align docs with current CLI behavior

Update stale documentation and user-facing MCP OAuth guidance to match the current dialog-based flows, SDK permission semantics, current links, and Qwen OAuth status.

Also replace Ink internal imports with public Ink APIs for the shared text input so the workspace builds against Ink 7.

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

* codex: address PR review feedback (#5589)

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

* codex: address PR review feedback (#5589)

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

* codex: address PR review feedback (#5589)

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

* codex: address PR review feedback (#5589)

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

* codex: address PR review feedback (#5589)

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

* codex: address PR review feedback (#5589)

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

* codex: address PR review feedback (#5589)

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

* codex: address PR review feedback (#5589)

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

* codex: address PR review feedback (#5589)

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

* codex: address PR review feedback (#5589)

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

* codex: fix BaseTextInput Ink import (#5589)

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

* codex: address PR review feedback (#5589)

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

* fix(core): surface MCP OAuth credential read failures

Fix SSE OAuth credential pre-check failures by reporting token storage read errors before connecting.

Update SDK coreTools docs and extension release link text from the follow-up review.

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

* fix(core): harden MCP OAuth error handling

Handle stderr warning failures as best-effort and keep SSE 401 OAuth guidance when credential re-read fails.

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

* fix(core): keep SSE OAuth pre-read best effort

Avoid blocking SSE MCP connections when the diagnostic credential pre-read fails, and cover BaseTextInput absolute-position edge cases.

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

* fix(core): handle SSE OAuth validation errors

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

* fix(core): surface MCP OAuth recovery guidance

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

* fix(core): cover MCP OAuth retry paths

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

* fix(core): address OAuth guidance review

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-06-24 07:09:53 +08:00
Dragon 7cb95bebbd fix(docs): update Coding Plan model list and fix stale references in developer docs (#5054)
* fix(docs): update Coding Plan model list and fix stale references in developer docs

- model-providers.md: expand Coding Plan model table from 3 to 9 models (add qwen3.6-plus, glm-5, kimi-k2.5, MiniMax-M2.5, qwen3-coder-next, glm-4.7) to match the provider registry
- auth.md: update Coding Plan model list to match all 9 models and use the correct date-stamped model ID (qwen3-max-2026-01-23)
- contributing.md: fix Node.js version requirement from 18+ to 22+ to match package.json engines
- sdk-typescript.md: fix nonexistent tool name run_terminal_cmd → run_shell_command
- integration-tests.md: fix file extension from .test.js to .test.ts to match actual test files
- qwen-serve-protocol.md: fix legacy tool name search_file_content → grep_search and remove nonexistent ripgrep tool reference

* fix(docs): add missing qwen3.7-plus to Coding Plan model docs

MODELSTUDIO_MODELS in alibaba-coding-plan.ts defines 10 models, but the
inline list in auth.md and the table in model-providers.md only listed 9,
omitting qwen3.7-plus (1M context, thinking enabled). Add it after
qwen3.6-plus in both, matching the source order.
2026-06-13 01:34:14 +08:00
jinye 62ed44e1f3 feat(telemetry): client-side HTTP span + opt-in W3C traceparent propagation (#4384) (#4390)
* feat(telemetry): propagate W3C traceparent on outbound LLM requests

Part 1 of #4384 (sub-issue of #3731 P3 deeper observability).

Today qwen-code's only OTel instrumentation is `HttpInstrumentation`,
which only patches Node's `http`/`https` modules. The `openai` and
`@google/genai` SDKs use `globalThis.fetch` (undici), so outbound LLM
requests carry no `traceparent` header and trace context dies at the
qwen-code process boundary.

Adds `@opentelemetry/[email protected].0` (peer-compatible
with the installed `@opentelemetry/[email protected].0`) and wires
it into `initializeTelemetry()` next to the existing
`HttpInstrumentation`. Default propagator (W3C tracecontext + baggage)
remains unchanged — no explicit `textMapPropagator` needed.

`ignoreRequestHook` skips OTLP exporter endpoints to avoid the
classic feedback loop (OTel SDK uses fetch to upload OTLP data; without
the hook each upload would create a span that gets uploaded, infinitely).
Configured `otlpEndpoint` / per-signal endpoints are stripped of trailing
slash and query string for robust prefix matching against undici's
`request.origin + request.path`.

Outbound LLM calls now also produce a client-side HTTP span (separating
network TTFB / transfer time from the existing `api.generateContent`
total-duration span).

Design doc: docs/design/telemetry-outbound-propagation-design.md
(Part A — traceparent; Part B — session id header — lands in a
follow-up PR per the design's split rationale.)

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(telemetry): harden OTLP feedback-loop guard + slim lockfile diff

Review feedback on #4390:

1. CI was failing on npm ci because the lockfile was generated with npm 11
   locally (it sprinkles `peer: true` annotations npm 10 reads differently
   and rejects). Regenerated with npm 10 (matching CI's Node 22.x default),
   so the diff vs main is now 18 lines (the actual instrumentation-undici
   entry) instead of 105 lines of npm-version drift noise.

2. (Copilot inline at sdk.ts:330) `otlpUrlPrefixes` was derived from raw
   Config strings, so a settings.json `"otlpEndpoint": "\"http://...\""`
   (quoted) or trailing `#fragment` would silently miss the prefix match
   and reintroduce the feedback loop the hook exists to prevent. Replaced
   the regex-based suffix trim with a WHATWG URL parser:
   - strips ?query, #fragment, trailing slash
   - trims symmetric ASCII quotes a user may have placed in settings.json
   - falls back to safe suffix trimming if URL parsing fails (misconfigured
     endpoint still gets SOME protection)

3. (CodeQL inline) Replaced the `/\?.*$/` regex in ignoreRequestHook with
   `indexOf('?')`/`indexOf('#')` slicing for ReDoS hygiene. The regex was
   linear in practice but flagged as polynomial — using indexOf removes
   the ambiguity and is arguably simpler.

Added 3 tests in sdk.test.ts covering the new normalizations (#fragment
on incoming path, quoted endpoint, #fragment on configured endpoint).

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* feat(telemetry): propagate X-Qwen-Code-Session-Id on outbound LLM requests

Part 2 of #4384. Stacks on top of PR #4390 (traceparent via undici).

Adds a product-namespaced HTTP header X-Qwen-Code-Session-Id to every
outbound LLM request when telemetry is enabled, so server-side ingestion
can correlate observed requests with qwen-code session metric/log records.
Pattern matched from claude-code (X-Claude-Code-Session-Id, verified at
src/services/api/client.ts:108 in their open-source repo).

Critical design decision (design doc section 4.3): the OpenAI / Anthropic
providers use a per-request fetch wrapper rather than the SDK defaultHeaders
option, because content-generator SDK clients are constructed once and NOT
recreated on /clear-triggered session resets (Config.resetSession updates
this.sessionId but the contentGenerator keeps using the stale header value).
Reading config.getSessionId() from inside the wrapper at request time gives
the live value.

Gemini provider uses static httpOptions.headers — @google/genai HttpOptions
interface does not expose a fetch hook (only headers, baseUrl, apiVersion,
timeout, extraParams). This is a known limitation: after session reset,
Gemini X-Qwen-Code-Session-Id stays stale until the contentGenerator is
recreated. Documented in telemetry.md and the design doc section 8.6;
spans/logs continue to carry the live session id for trace/log correlation.
Lazy-invalidate fix is a follow-up sub-issue.

Header is omitted when telemetry is disabled OR when getSessionId returns
an empty string (some HTTP middleware rejects empty header values).

Integration sites:
- packages/core/src/core/openaiContentGenerator/provider/default.ts
  (base class — automatically covered by deepseek/minimax/mistral/
  modelscope/openrouter subclasses; openrouter calls super.buildHeaders)
- packages/core/src/core/openaiContentGenerator/provider/dashscope.ts
  (overrides buildClient — must be touched separately; QwenContentGenerator
  inherits via this provider)
- packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts
- packages/core/src/core/geminiContentGenerator/index.ts (factory function,
  not the GeminiContentGenerator class — no signature change)

End-to-end verification (local HTTP server in tmux):
  PASS: traceparent + X-Qwen-Code-Session-Id on every LLM request
  PASS: session id refreshes after simulated /clear (staleness regression
        guarded by llm-correlation-fetch.test.ts)
  PASS: OTLP upload traffic not traced (no feedback loop — PR A
        ignoreRequestHook working)

Robot generated with Qwen Code https://github.com/QwenLM/qwen-code

* fix(telemetry): R2 review fixes — critical correctness + tsc + boundary safety

Adopts 7 review findings from wenshao on #4390 (+ duplicates from now-closed
#4393). Critical bugs first, polish second.

CRITICAL:

1. tsc TS2322 — wrapper return type incompatible with Anthropic SDK Fetch.
   `typeof fetch` (Node WHATWG, 2 overloads) is not structurally assignable
   to Anthropic's narrower `Fetch = (input: RequestInfo, init?) => ...`,
   even though they're call-compatible at runtime. Make wrapper generic
   `<TFetch extends FetchLikeLoose>` so callers preserve their exact fetch
   signature; cast the Anthropic call site through `unknown` with a comment
   explaining why.

2. tsc TS2352 / TS2493 — `baseFetch.mock.calls[0]![1] as RequestInit` was
   out-of-bounds when wrapped was called with no init arg. Replaced with a
   `makeFetchMock()` helper returning typed accessors.

3. normalizeOtlpPrefix catch fallback was DANGEROUS — a config of `"http"`
   produced prefix `"http"` which `startsWith`-matched every outbound HTTP
   request → silently disabled ALL instrumentation (no client spans, no
   correlation header — defeats the entire feature). Fixed: catch returns
   undefined + diag.warn. Misconfigured endpoint loses its feedback-loop
   guard (acceptable) instead of disabling all guards (catastrophic).

4. `url.startsWith(prefix)` matching was NOT boundary-safe — port collision
   (`:4318` matches `:43180`), hostname suffix collision (`otlp.example.com`
   matches `otlp.example.com.evil.net`), path-segment collision (`/v1`
   matches `/v1foo/x`). Replaced with origin-equality + path-prefix +
   boundary-char check (next char must be `/`, `?`, `#`, or end-of-string).

5. HttpInstrumentation also lacked the OTLP feedback-loop guard. The OTLP
   HTTP exporter (`@opentelemetry/exporter-trace-otlp-http`) uses node:http
   (patched by HttpInstrumentation, NOT undici). Without this, every OTLP
   upload batch creates a parasitic client span → feedback loop. Added
   `ignoreOutgoingRequestHook` that reuses the same `matchesOtlpPrefix` /
   `stripPathSuffix` helpers as the undici instrumentation.

SAFETY:

6. Request input + undefined init dropped the Request's own headers
   (Authorization etc.) because `new Headers(undefined)` → `{...init, headers}`
   replaced them with just our session header. Fix: when input is a Request
   and init.headers is unset, seed from input.headers before adding ours.

7. Wrapped fetch had no try/catch — a throwing Config getter or Headers
   constructor would propagate as TypeError and break the LLM request path.
   Wrapped header construction in try/catch; on failure, fall through to
   baseFetch with original init (no header) + diag.warn. Telemetry must
   never break the model call.

COVERAGE:

- 3 new sdk.test.ts boundary tests (port/host/path)
- 1 new sdk.test.ts normalizeOtlpPrefix catch-branch coverage
- 1 new sdk.test.ts HttpInstrumentation OTLP guard test
- 1 new sdk.test.ts proxy-mode wrapped-fetch test (default.test.ts)
- 1 new anthropic test asserting wrapped fetch installed on Anthropic SDK
- 2 new llm-correlation-fetch.test.ts (Request-headers preservation + try/catch fall-through)

All 668 tests pass (1 pre-existing Anthropic User-Agent failure on main is
unrelated). tsc clean.

Declined: #10 DRY-refactor of baseFetch extraction across 3 sites — the
duplication was pre-existing (default/dashscope buildClient was already
near-identical), refactoring is a separate cleanup PR not gated by this
feature. Will reply on the thread.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* chore(deps): allow patch updates for @opentelemetry/instrumentation-undici

Switch from exact pin `0.14.0` to `^0.14.0` for consistency with the rest
of the `@opentelemetry/*` deps in this block (all carated).

For 0.x semver, npm treats `^0.14.0` as `>=0.14.0 <0.15.0`, so patch
updates within the 0.14.x line — which are tied to the same
`@opentelemetry/[email protected].x` peer — flow in via `npm update`
without requiring a manual package.json edit. A bump across the 0.x
minor (e.g. 0.15.x) would shift the instrumentation peer compatibility
and still requires explicit attention, which the caret correctly blocks.

Per review feedback on #4390 (wenshao).

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* test(telemetry): stub getTelemetryEnabled + getSessionId in Gemini factory tests

The X-Qwen-Code-Session-Id commit added a `staticCorrelationHeaders(gcConfig)`
call inside the Gemini content generator factory. That helper reads
`gcConfig.getTelemetryEnabled()` and `gcConfig.getSessionId()` per request.

Both pre-existing Gemini tests in `contentGenerator.test.ts` build a minimal
partial Config stub via `as unknown as Config` and only stub the methods the
factory used to need. The new call path now hits the unstubbed methods at
runtime, surfacing as `TypeError: config.getTelemetryEnabled is not a function`
on all three CI platforms.

Add the two missing stubs to both test cases. The Gemini factory continues
to ignore the values when telemetry is off — these stubs only have to exist,
not return anything in particular.

Local check ran the full test suite for the four directories `/loop` covers
plus `src/core/contentGenerator.test.ts` itself; all green. Also re-ran the
other test files that build partial Config mocks via the same idiom
(`client.test.ts`, `config.test.ts`, `nextSpeakerChecker.test.ts`,
`content-generator-config.test.ts`) — none exercise the new code path.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(telemetry): R3 review fixes — port + protocol + quote + safety

Four issues found by wenshao reviewing the R2 boundary-safety pass on PR
#4390. All four close gaps where the OTLP feedback-loop guard or the
correlation-header path could fail silently.

1. **Port normalization mismatch** (sdk.ts ignoreOutgoingRequestHook):
   `normalizeOtlpPrefix` builds prefixes via `URL.origin`, which strips
   default ports (`:80` for http, `:443` for https). The hook reconstructed
   request origin manually as `${proto}://${host}${portPart}`, keeping the
   port. Result: prefix `http://collector` (no explicit port) didn't match
   a request to `http://collector:80/v1/traces` because their `.origin`
   differed → guard bypassed → feedback loop. Now the reconstructed origin
   is also routed through `URL` so both sides apply the same default-port
   stripping.

2. **HTTPS proto silent fallback** (sdk.ts ignoreOutgoingRequestHook):
   The `(req.protocol && ...) || 'http'` fallback would silently mis-bucket
   HTTPS requests as HTTP when `req.protocol` was unset, so HTTPS OTLP
   endpoints couldn't match their prefix. Changed to fail open: when proto
   can't be determined, return false (request gets instrumented). Worst
   case is a parasitic client span — observable, recoverable — versus the
   previous unbounded silent feedback loop. Picked fail-open over the bot's
   port-based heuristic because non-standard HTTPS ports break the
   heuristic but not fail-open.

3. **Quote-stripping divergence** (sdk.ts normalizeOtlpPrefix):
   `parseOtlpEndpoint` (line 109) uses `/^["']|["']$/g` which strips
   asymmetric leading/trailing quotes; `normalizeOtlpPrefix` previously
   only stripped symmetric pairs. A settings.json typo like `"value'` would
   let the exporter connect (parseOtlpEndpoint trims) but leave the guard
   returning `undefined` (normalizeOtlpPrefix rejected) → parasitic loop.
   Aligned `normalizeOtlpPrefix` to the same lenient regex.

4. **`staticCorrelationHeaders` missing try/catch** (llm-correlation-fetch.ts):
   `wrapFetchWithCorrelation` already catches all internal exceptions and
   falls through to baseFetch — same "telemetry must never break LLM path"
   contract was missing on the static-headers helper. A throw here would
   propagate up to the Gemini content-generator factory and crash
   content-generator init for the whole session. Wrapped the body in
   try/catch with `diag.warn` fall-through to `{}`.

Tests: added 4 regression tests covering each scenario:
- default-port HTTP request matched against portless prefix (1)
- hook returns false when req.protocol missing on https endpoint (2)
- asymmetric-quoted endpoint normalizes for guard parity (3)
- staticCorrelationHeaders returns {} when config getter throws (4)

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* docs(telemetry): fix misleading "BOTH" wording in wrapFetchWithCorrelation

The comment described the header-seeding logic as merging "BOTH the
init.headers AND the Request's own headers", but the two branches are
mutually exclusive — `new Headers(init?.headers)` runs unconditionally
(empty Headers when init.headers is undefined), and the Request-headers
copy only runs when init.headers is undefined. So in practice it's
either-or, not BOTH.

Reworded to match the actual logic per #4390 review feedback (wenshao).
Behavior unchanged.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(telemetry): strip port from req.host fallback + document undici scope

Two issues found by wenshao reviewing the R3 boundary-safety fixes on PR
#4390.

1. **`req.host` may already include `:port`** (sdk.ts ignoreOutgoingRequestHook):
   When `req.hostname` is absent and `req.host` is the fallback, the value
   may already be `"collector:4318"`. Naively appending `:${req.port}`
   produced `"http://collector:4318:4318"` → `new URL()` rejects → catch
   returns false → silent guard bypass for that request. Currently
   unreachable because `@opentelemetry/otlp-exporter-base` always sets
   `hostname` from WHATWG URL parsing, but the fallback exists in the
   code and must be correct — a future OTLP transport that emits `host`
   without `hostname` would silently trigger the feedback loop. Strip the
   port when falling back; bracketed IPv6 literals like `"[::1]:443"`
   keep their bracketed host intact.

2. **Undici scope honesty** (telemetry.md):
   Previous docs framed the propagation as "outbound LLM requests", but
   `UndiciInstrumentation` actually patches `globalThis.fetch` for the
   whole process — `WebFetch`, MCP clients, IDE extension calls all get
   spans + `traceparent` injection too. Added a "Scope: all fetch() calls,
   not just LLM" subsection covering: (a) trace ID leakage to third-party
   URLs (the user-supplied destinations of `WebFetch` see our trace ID;
   not secret per W3C but worth knowing); (b) non-LLM span volume
   inflating OTLP batches with a workaround tip. Per-destination scoping
   toggle deferred as a follow-up — out of scope for this PR.

Added regression test for the host:port-fallback path. Test exercises
the previously broken combination (hostname absent, host carries port)
through the existing test harness.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* feat(telemetry): scope X-Qwen-Code-Session-Id to first-party hosts by default

Address LaZzyMan's REQUEST_CHANGES review of PR #4390.

The original design injected `X-Qwen-Code-Session-Id` on every outbound
LLM request gated only by `telemetry.enabled`. Review caught that this
broadcasts a stable cross-request client identifier to every configured
third-party provider (OpenAI, Anthropic, OpenRouter, MiniMax, ModelScope,
Mistral, vanilla Gemini, ...), which the claude-code precedent does NOT
justify — claude-code is a first-party Anthropic→Anthropic flow; qwen-code
is an open-source CLI connecting to many providers.

Fix: add a host allowlist with a deliberately narrow default. The header
is now only attached to destinations whose hostname matches:

  dashscope.aliyuncs.com
  dashscope-intl.aliyuncs.com
  *.dashscope.aliyuncs.com
  *.dashscope-intl.aliyuncs.com
  *.alibaba-inc.com
  *.aliyun-inc.com

This is exactly the set where the LLM provider, the upstream telemetry
backend (ARMS Tracing), and qwen-code itself are the same legal entity —
mirroring the first-party claude-code pattern and preserving the real
product value (server-side trace stitching against DashScope) without
exposing the session id to third parties.

Operators with broader correlation requirements override via:

  "telemetry": {
    "sessionIdHeaderHosts": ["*"]                          // restore broadcast
    "sessionIdHeaderHosts": []                              // fully disable
    "sessionIdHeaderHosts": ["api.example.com", "*.foo"]    // custom allowlist
  }

Implementation:

- NEW `telemetry/trusted-llm-hosts.ts`: `DEFAULT_SESSION_ID_HEADER_HOSTS`
  + `matchesTrustedHost(hostname, patterns)` + `extractRequestHost(input)`.
  Pattern syntax is intentionally tiny (bare hostname OR `*.suffix`,
  dot-anchored to reject `evil-alibaba-inc.com` style attacks). Unit-tested
  in dedicated test file including TLD/sub-domain attack vectors.

- `wrapFetchWithCorrelation` (openai + anthropic providers): resolves the
  allowlist at wrap time (Config snapshot), inspects each request's
  destination URL inside `correlationFetch`, falls through to baseFetch
  for non-trusted destinations. Wildcard escape hatch via `["*"]`.

- `staticCorrelationHeaders` (Gemini factory): now takes an optional
  `destinationUrl` and applies the same host gate. The Gemini SDK default
  endpoint `generativelanguage.googleapis.com` is NOT on the default
  allowlist, so vanilla Gemini calls receive no header — matching the
  "first-party only" scope. Operators who put the Gemini SDK on a
  DashScope-compatible endpoint via `baseUrl` get the header naturally.

- `Config.getTelemetrySessionIdHeaderHosts()` getter +
  `TelemetrySettings.sessionIdHeaderHosts` interface field + JSON schema
  entry in `settingsSchema.ts`. Wired through `resolveTelemetrySettings`.

- Defensive optional-chaining + try/catch on the Config getter call at
  wrap time so partial test mocks (or pre-getter Config implementations)
  fall back to the default allowlist rather than crashing buildClient.

Tests: 12 new cases covering host match/skip on default allowlist,
sub-domain handling, TLD-suffix attack rejection, `["*"]` broadcast
override, `[]` full-disable, custom operator allowlist, unparseable
destination (fail closed), and the three Gemini factory paths
(googleapis.com default → omit; DashScope `baseUrl` → inject; custom
allowlist → inject).

Docs updated in `docs/developers/development/telemetry.md` Session
correlation header section, including override examples and the new
Gemini host-gate semantics.

Closes the LaZzyMan REQUEST_CHANGES blocker. The cross-vendor
fingerprint-broadcast failure mode is now opt-in rather than default,
restoring the first-party-only semantics that make the claude-code
precedent applicable.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(telemetry): R5 review fixups — Vertex destination + ["*"] trim + docs

Self-review pass on commit 1c8528a56 (host-scoped session-id header):

1. **Vertex AI destination guessing**
   (geminiContentGenerator/index.ts)
   `@google/genai` routes to `{region}-aiplatform.googleapis.com` (not
   `generativelanguage.googleapis.com`) when `vertexai: true` and no
   `baseUrl`. The previous "guess generativelanguage" default would have
   mis-bucketed Vertex traffic under any operator-supplied allowlist that
   covered the public Gemini endpoint but not the Vertex one. Today
   invisible (both off the default allowlist), but a latent gotcha for
   operators tuning `telemetry.sessionIdHeaderHosts`.
   Fix: pass `undefined` when `config.baseUrl` is unset (fail-closed —
   no header). Operators who want correlation against Google endpoints
   must set `baseUrl` explicitly, which is also the SDK's input for
   destination resolution.

2. **`["*"]` broadcast escape hatch tolerates whitespace**
   (llm-correlation-fetch.ts)
   `[" * "]` (a settings.json hand-edit with a stray space) previously
   silently fell back to "no host matches" — the opposite of operator
   intent. Now `.trim()` before comparing, so common whitespace mistakes
   still trigger broadcast.

3. **Doc note on wrap-time allowlist snapshot**
   (llm-correlation-fetch.ts JSDoc)
   The session id is read live per-request, but `trustedHosts` is
   snapshotted once at `wrapFetchWithCorrelation` call time. Spell this
   out in the JSDoc so a future maintainer doesn't read the live
   `getSessionId()` and assume the allowlist is the same shape.

4. **Defensive test coverage**
   (trusted-llm-hosts.test.ts, llm-correlation-fetch.test.ts)
   Added: extractRequestHost with explicit port / userinfo / query /
   fragment / IPv6 bracket form. Whitespace `[" * "]` broadcast test.
   IPv6 case documents the "bracketed → never matches" behavior is
   intentional fail-closed for the named-host allowlist scope.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* chore: regenerate settings.schema.json for sessionIdHeaderHosts

Lint check `Check settings schema is up-to-date` failed because the
checked-in `packages/vscode-ide-companion/schemas/settings.schema.json`
wasn't regenerated after adding `telemetry.sessionIdHeaderHosts` to
`settingsSchema.ts` in commit 1c8528a56. Regenerated via
`npm run generate:settings-schema`.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* docs(design): update telemetry-outbound-propagation design for R3 host-allowlist scoping

Adds a "修订历史" header table at the top and a new §11 "R3 修订 —
Host-Allowlist Scoping for X-Qwen-Code-Session-Id" capturing what changed
after LaZzyMan's REQUEST_CHANGES review, why, and how. Inline pointers
added at §3.1, §3.2, §4.3, §4.4, §9 (claude-code comparison table) to
point readers at §11 — original prose preserved as a record of the
decision path rather than rewritten in place.

Concretely §11 covers:
- The three-step LazzyMan critique and why R1's "broadcast to all
  providers" was structurally wrong for an open-source multi-provider CLI
- The default allowlist (`DEFAULT_SESSION_ID_HEADER_HOSTS`) and its
  semantic alignment with the DashScope provider detector
- Pattern grammar (bare hostname / `*.suffix` dot-anchored), the
  TLD-suffix attack vectors it rejects, why no regex / port-aware globbing
- `wrapFetchWithCorrelation` host gate, wrap-time vs request-time
  semantics, `[" * "]` whitespace tolerance
- `staticCorrelationHeaders` `destinationUrl` parameter, Gemini factory's
  fail-closed treatment of unset `baseUrl` (avoids the Vertex
  vs `generativelanguage.googleapis.com` ambiguity)
- All R3 file changes mapped to the original §5 file-change list
- Mapping of LazzyMan's three concerns to R3's responses
- §10 future-work additions: `traceparent` per-destination toggle,
  `X-Qwen-Code-Request-Id`, IPv6 allowlist syntax

No code changes; documentation only.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(telemetry): defensive allowlist normalization + positive proxy test

Three issues found by wenshao reviewing the R3 host-allowlist scoping.

1. **[Critical] `broadcastAll` outside safety try/catch**
   (llm-correlation-fetch.ts wrapFetchWithCorrelation)
   The try/catch only fires when `getTelemetrySessionIdHeaderHosts()`
   throws. If it returns a malformed value — a bare string (settings.json
   typo `"sessionIdHeaderHosts": "host"` instead of `["host"]`), an array
   containing `null`/`undefined`/number entries, or whitespace-padded
   entries — `.some((p) => p.trim() === '*')` throws TypeError at
   buildClient time, bricking the LLM session before the first prompt.
   `staticCorrelationHeaders` already handled this via its end-to-end
   try/catch but the sister helper diverged. Settings loader does no
   runtime schema validation so this is reachable via a single typo.

   Fix: normalize the allowlist at wrap time:
     1. catch a throwing getter (existing)
     2. reject non-array → default allowlist (NEW — bare string typo)
     3. filter out non-string elements (NEW — [null, ...] typo)
     4. trim every surviving entry uniformly (NEW — see #2 below)
   Then `trustedHosts.includes('*')` instead of `.some((p) => p.trim() === '*')`,
   since patterns are already pre-trimmed.

2. **Trim asymmetry between `*` detection and host-pattern match**
   (llm-correlation-fetch.ts)
   `[" * "]` was tolerated (trimmed before `===` compare) but
   `[" dashscope.aliyuncs.com "]` silently never matched. The
   normalization above fixes this by trimming uniformly upstream.

3. **Proxy fetch test: only negative assertions**
   (openaiContentGenerator/provider/default.test.ts)
   The test asserted `callArg.fetch !== proxyFetch` and `!== globalThis.fetch`
   but both passed for ANY wrapper, including a buggy one that
   accidentally wraps globalThis.fetch instead of proxyFetch. Added a
   positive assertion: call the wrapped fetch and verify proxyFetch was
   the delegation target.

Tests: 4 new cases — whitespace-padded host pattern, bare-string
malformed config (both wrapper and static), null/number-containing
array malformed config (both wrapper and static), positive proxy fetch
delegation. All pass; pre-existing Anthropic User-Agent failure
unrelated.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* refactor(telemetry): split outbound correlation out of telemetry scope (R4)

Address LaZzyMan round-8 follow-up review on PR #4390: even though R3's
host allowlist made the default behavior safe, the meta-architectural
concern remains: telemetry's namespace and consent flow shouldn't quietly
extend to wire-level behavior aimed at third-party LLM provider request
streams. The recipient sets differ; the consent decisions differ; they
deserve separate namespaces, separate threat models, separate PRs.

This commit (called R4 in the design doc) collapses the PR scope so it
lands ONLY telemetry observability work:

REMOVED from this PR:
  - packages/core/src/telemetry/llm-correlation-fetch.ts(.test.ts)
  - packages/core/src/telemetry/trusted-llm-hosts.ts(.test.ts)
  - telemetry.sessionIdHeaderHosts setting + Config getter +
    resolveTelemetrySettings wiring + settingsSchema entry
  - wrapFetchWithCorrelation usage from four provider construction
    points (default.ts, dashscope.ts, anthropicContentGenerator.ts,
    geminiContentGenerator/index.ts)
  - All session-id provider tests across the four providers + the
    contentGenerator.test.ts mock stub
  - "Session correlation header" section in telemetry.md

ADDED:
  - OutboundCorrelationSettings interface in packages/core/src/config/config.ts,
    standalone top-level namespace separate from TelemetrySettings —
    SECURITY-RELEVANT label, all defaults off
  - Config.getOutboundCorrelationPropagateTraceContext() getter
  - outboundCorrelation top-level entry in settingsSchema.ts with
    propagateTraceContext: { default: false } and explicit
    SECURITY-RELEVANT framing in the description
  - CLI config-load pipeline passes settings.outboundCorrelation into
    ConfigParameters
  - NOOP_PROPAGATOR (TextMapPropagator no-op) in sdk.ts, conditionally
    installed on NodeSDK when propagateTraceContext is false (default).
    When true, omits textMapPropagator from NodeSDK options so the SDK
    keeps its default W3C composite propagator
  - 2 new sdk.test.ts cases covering the propagator gate behavior

UNCHANGED:
  - UndiciInstrumentation registration + OTLP feedback-loop guard +
    HttpInstrumentation OTLP guard from R2/R3 stay intact — they are
    pure telemetry (client HTTP spans into the operator's own OTLP
    collector), no wire-level data egress
  - Documentation rewrites telemetry.md to split "client-side HTTP
    span on outbound fetch" (telemetry) from a new "Outbound
    correlation (SECURITY-RELEVANT)" top-level section
  - design doc gets R4 revision row + new §12 "R4 Scope Conflation
    Split" capturing the rationale and follow-up PR outline

The session-id apparatus (R3 code) lives in git history at commits
1c8528a56 / cb162e716 / 7a1b4f8d0 / 40e1efc1f / 106598ca2; the
follow-up PR can cherry-pick or restore those files under the new
outboundCorrelation.* namespace as LazzyMan suggested.

Vscode-ide-companion settings.schema.json regenerated.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* docs(telemetry): disclose telemetry.enabled dependency on propagateTraceContext

Self-review pass on R4 commit 9bdd3bd6f flagged one footgun: both
`docs/developers/development/telemetry.md` and the settingsSchema.ts
description for `outboundCorrelation.propagateTraceContext` describe
the toggle's behavior without noting that the flag is a silent no-op
when `telemetry.enabled` is false. An operator who sets only
`outboundCorrelation.propagateTraceContext: true` and forgets the
telemetry switch gets zero behavior change — no error, no warning, no
traceparent.

Fix: add the dependency disclosure to both surfaces, plus a JSON
example showing both flags wired together for the ARMS+DashScope
cross-process trace continuation use case.

Also fix a minor comment accuracy nit at `sdk.test.ts:683`: said the
SDK installs `W3CTraceContextPropagator` instance when opt-in is true,
but the actual default is `CompositePropagator(W3CTraceContextPropagator
+ W3CBaggagePropagator)` per `@opentelemetry/sdk-node` source.

Vscode-ide-companion settings.schema.json regenerated to reflect the
expanded description string.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* test(config): cover getOutboundCorrelationPropagateTraceContext defaults

R4 (commit 9bdd3bd6f) added the getter but the test file didn't grow a
corresponding describe block — sibling telemetry getters all have unit
tests but this new one was missed.

Add 4 cases covering the security-relevant default-to-false invariant
and explicit-set behavior:
- omitted outboundCorrelation → false
- empty outboundCorrelation: {} → false (the `?? false` collapse on the
  getter, complementing the same on the constructor)
- explicit true → true
- explicit false → false

PR #4390 review (wenshao).

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* docs(design): reflect post-R4 polish commits in §12

R4 (commit 9bdd3bd6f) was followed by two polish commits that the
design doc §12 didn't track:

- 0be0df270 (docs): telemetry.enabled dependency disclosure on
  propagateTraceContext — added to telemetry.md + settingsSchema
  description because a self-review pass identified the silent-no-op
  footgun (operator sets propagateTraceContext: true but forgets
  telemetry.enabled: true, sees zero behavior change with no error).
- c0352fd5b (test): 4 config.test.ts cases covering the
  getOutboundCorrelationPropagateTraceContext default-false invariant
  (omitted / {} / explicit true / explicit false) — wenshao review
  flagged the test gap.

Updates §12.4 with a new "Hidden dependency: telemetry.enabled" sub-
section explaining the gating relationship and pointing forward at the
follow-up PR (future outboundCorrelation.* settings inherit the same
dependency). Updates §12.5 implementation table to add the
config.test.ts row and clarify the telemetry.md / vscode-schema rows
were touched again in the polish pass.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* refactor: simplify post-R4 polish per /simplify review

/simplify review pass on commits 0be0df270 + c0352fd5b + 62cf6b4ee
flagged 4 concerns. Fix all 4:

1. **settingsSchema.ts description**: footgun warning ("Depends on
   telemetry.enabled: true") was at char 600+ of a 650-char description.
   VS Code settings UI truncates to ~300 chars inline → the most
   important warning was hidden in the most-glanced view. Hoist to first
   sentence ("Requires telemetry.enabled: true.").

2. **config.test.ts**: drop the task-narration comment
   ("PR #4390 R4: keep wire-level toggle out of telemetry namespace.")
   that just restated the change context. The remaining 2-line comment
   explaining WHY default-to-false is security-relevant survives.

3. **config.test.ts**: collapse 4 separate `it()` blocks into a single
   `it.each([...])` covering the same 4 precondition × expectation
   combinations. Removes boilerplate (`new Config({...baseParams, ...})`
   repeated 4×) without losing assertion power; case-3 ("explicit false")
   was a weak duplicate of case-2 ("empty object") since both hit the
   same `?? false` branch, but keeping all 4 in the parametric table
   documents intent more clearly than dropping case-3.

4. **design doc §12.4 + §12.5**: strip specific commit SHAs
   (`0be0df270`, `c0352fd5b`) — design docs should be evergreen, not
   doubled-up commit logs (those live in `git log`). Keep the design
   intent ("two panels both document the dependency" / "test block
   added") without naming the specific commits.

Regenerated vscode-ide-companion/schemas/settings.schema.json to
reflect the hoisted description sentence.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
2026-05-25 22:16:54 +08:00
jinye 64401e1d17 feat(telemetry): support custom resource attributes and add metric cardinality controls (#4367)
* feat(telemetry): support custom resource attributes and add metric cardinality controls

Resolves #4365.

Adds two coupled OpenTelemetry capabilities to make qwen-code's telemetry
production-ready in multi-team / multi-tenant deployments:

1. Custom resource attributes via standard `OTEL_RESOURCE_ATTRIBUTES` and
   `OTEL_SERVICE_NAME` env vars and a new `telemetry.resourceAttributes`
   setting. Operators can now tag every span / log / metric with `team`,
   `env`, `cost_center`, or anything else their backend needs.
2. Metric cardinality controls. `session.id` is moved off the OpenTelemetry
   Resource (where it auto-attached to every metric data point and caused
   unbounded time-series fan-out on Prometheus / ARMS Metric / etc.) and
   gated behind a new opt-in `telemetry.metrics.includeSessionId` toggle.
   Spans and logs still carry `session.id` for trace and log correlation.

Reserved keys (`service.version`, `session.id`) are stripped from both env
and settings sources with a `diag.warn`. `OTEL_SERVICE_NAME` follows the
OTel spec precedence (highest priority for `service.name`). Settings JSON
values are runtime-coerced to strings as defense against hand-edited
non-conforming JSON.

Breaking change: metrics no longer carry `session.id` by default. Operators
who need it can restore the previous behavior with
`QWEN_TELEMETRY_METRICS_INCLUDE_SESSION_ID=true` or
`telemetry.metrics.includeSessionId: true` in settings.json; recommended
only for short-term debugging since it re-introduces the cardinality
problem. For long-term session-level analysis, prefer trace and log
backends which handle per-event data without cardinality pressure.

Design doc: docs/design/telemetry-resource-attributes-design.md

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* docs(telemetry): align reserved-key descriptions with implementation

Round 1 review fixes (#4367). After session.id was added to
RESERVED_RESOURCE_ATTRIBUTE_KEYS in Codex review, four user-facing
descriptions still claimed only service.version was reserved:

- packages/core/src/telemetry/config.ts (merge comment)
- packages/core/src/config/config.ts (TelemetrySettings JSDoc)
- packages/cli/src/config/settingsSchema.ts (schema description)
- packages/vscode-ide-companion/schemas/settings.schema.json (regenerated)

Also corrects scope claim: resource attributes apply to every signal
the SDK exports (OTLP and file outfile share the same Resource), not
just OTLP.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* docs(telemetry): clarify warning destination and surface percent-encoding hint

Round 2 self-review fixes (#4367). Two small but real UX gaps:

1. Reserved-key / malformed-pair / coerce warnings route to the debug
   log (per #3986), not the console — so a user who types
   `OTEL_RESOURCE_ATTRIBUTES=service.version=2.0` sees no feedback that
   the value was silently dropped. Adds a "Troubleshooting" section in
   telemetry.md telling users where to look, and a note in the parser
   docstring documenting where warns go.

2. A literal (unencoded) comma in an env var value is a common foot-gun:
   the parser splits on it, producing a malformed second half that is
   silently dropped. Updates the warn text to include a "hint:
   percent-encode literal commas as %2C" callout, and adds the same
   guidance to the docs.

Deferred to a follow-up: startup-time stderr summary of dropped
attributes. Stderr during TUI render could break Ink rendering, so the
right surface needs separate design.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* test(telemetry): cover first-`=` split contract in OTEL_RESOURCE_ATTRIBUTES parser

Per review feedback on #4367. The parser uses `indexOf('=')` so
the first `=` separates key and value while subsequent `=` stay in
the value. The behavior was correct but untested; a future refactor
to `split('=')` would silently break base64-padded, JWT, or
connection-string values.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* feat(telemetry): tighten resource-attribute input validation + startup summary

Adopts review feedback from #4367 (wenshao via Qwen Code /review).

Five accepted suggestions, bundled because they all touch the same
parse/coerce/strip pipeline:

1. Key percent-decoding (CRITICAL). `parseOtelResourceAttributes` now
   percent-decodes both keys and values per the OTel / W3C Baggage spec.
   Without this, `OTEL_RESOURCE_ATTRIBUTES=service%2Eversion=99` lands
   on Resource as the literal key `service%2Eversion`, bypassing the
   reserved-key filter; a collector that decodes keys downstream could
   then resurrect `service.version` and spoof the version label.

2. Startup summary of dropped attributes. Every `diag.warn` in
   resource-attributes.ts routes only to the OTel debug log (per
   #3986), giving operators zero feedback when their attributes are
   silently dropped. Helpers now optionally accumulate diagnostics
   into a `ResourceAttributeWarnings` array; the resolver collects
   them and the SDK emits a one-time console summary at init (before
   Ink renders, so no TUI conflict).

3. `||` instead of `??` for service.name fallback. Settings can put
   an empty string through `??`, producing a blank `service.name`
   that some backends reject. `||` falls through to the default.

4. `coerceStringResourceAttributes` now trims keys and skips
   empty/whitespace-only keys, matching `parseOtelResourceAttributes`.
   Previously `{"  ": "x"}` or `{"team ": "y"}` from settings.json
   would land as malformed Resource attributes.

5. `OTEL_SERVICE_NAME` is trimmed before the truthy check, so values
   like `'  '` or `'\t'` are treated as unset rather than producing
   a whitespace-only service name on Resource.

One suggestion declined (in-thread reply on PR):

- "Redundant `?? {}` in sdk.ts:160" — intentional defense-in-depth for
  `vi.mock('../config/config.js')` callers in `telemetry.test.ts` where
  auto-stub returns undefined. The reviewer is right that production
  code paths never hit it, but tests do.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(telemetry): trim whitespace-only service.name + add invalid-key-encoding test

Adopts two review suggestions on #4367 (wenshao via Qwen Code /review):

1. `service.name` fallback uses `.trim() || SERVICE_NAME` instead of plain
   `||`. Plain `||` lets whitespace-only values (`" "`, `"\t"`) through as
   truthy, producing a blank service name on Resource that some backends
   reject. Both settings (no value trimming) and env (`%20` decodes to `" "`)
   can deliver such values. Test added.

2. Adds `key%ZZ=val` to the parameterized parser test to cover the
   invalid-percent-encoding-on-key catch branch. Previously only the
   value-side catch was tested.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
2026-05-21 13:54:37 +08:00
jinye 54fd5c50f0 feat(telemetry): add detailed sensitive span attributes (#4097)
Layer detailed content attributes onto the existing hierarchical spans
(qwen-code.interaction / qwen-code.llm_request / qwen-code.tool) gated
by includeSensitiveSpanAttributes:

- Interaction span: user prompt (new_context)
- LLM request span: system prompt + hash + preview + length (full text
  deduped per session via SHA-256), tool schemas (per-tool tool_schema
  events, also hash-deduped), model output
- Tool span: tool input, tool result on every exit path (success +
  pre-hook block + post-hook stop + tool error + try-block cancel +
  catch-block cancel + execution exception)

All large content truncated at 60KB with *_truncated and
*_original_length metadata. Heavy serialization (safeJsonStringify on
tool I/O, partToString on user prompt) is guarded by the sensitive
flag at the call site so it doesn't run when telemetry is off.

Also adds:
- getActiveInteractionSpan() helper for client.ts to attach prompt
  attributes to the interaction span.
- Updated config schema description and docs (telemetry.md +
  settings.md) to reflect expanded scope and add security/cost notes.
- 28 unit tests for detailed-span-attributes, 4 tests for
  getActiveInteractionSpan, integration mocks updated.
2026-05-17 00:36:48 +08:00
jinye aecea70114 docs(telemetry): align config and docs semantics for target, outfile, and CLI flags (#4066)
* docs(telemetry): align config and docs semantics for target, outfile, and CLI flags

- Remove stale warning note "This feature requires corresponding code
  changes" — the OTLP implementation is now complete (#3779, #4061)
- Clarify that `target` is an informational destination label and does
  not control exporter routing; `otlpEndpoint` or `outfile` must be set
  to configure where data is sent
- Mark `--telemetry-target` CLI flag as deprecated in the configuration
  table to match the deprecateOption() call in cli/src/config/config.ts
- Fix `outfile` / `QWEN_TELEMETRY_OUTFILE` descriptions: remove the
  incorrect "when target is local" qualifier — outfile overrides OTLP
  export regardless of the target value
- Simplify the file-based output example by removing the now-redundant
  `"target": "local"` and `"otlpEndpoint": ""` fields

Closes the "Align telemetry config and docs semantics for target,
useCollector, otlpEndpoint, otlpProtocol, and outfile" checklist item
in #3731.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* docs(telemetry): address Copilot review comments on outfile and target descriptions

- Fix outfile table row in telemetry.md: "overrides `otlpEndpoint`" →
  "overrides OTLP export" (outfile disables all OTLP exporting, not
  just the base endpoint)
- Use fully-qualified setting names (`telemetry.otlpEndpoint`,
  `telemetry.outfile`) in the target description in settings.md for
  consistency with the rest of the table

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* docs(telemetry): update QWEN_TELEMETRY_TARGET env var description and add outfile note

- Align QWEN_TELEMETRY_TARGET env var description with the updated
  telemetry.target setting semantics (informational label, not routing)
- Add a note after the file-based output example clarifying that outfile
  automatically disables OTLP export

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
2026-05-13 08:27:41 +08:00
jinye 32a49b4ddb refactor(telemetry): remove dead useCollector setting and unreachable TelemetryTarget.QWEN (#4061)
useCollector was plumbed through config (interface, constructor, getter,
env var resolution) but never consumed by the telemetry SDK — the setting
had no runtime effect. TelemetryTarget.QWEN existed in the enum but
parseTelemetryTargetValue() only accepted 'local' and 'gcp', making
'qwen' unreachable (it would throw FatalConfigError).

Remove both dead code paths along with their tests and documentation.

Part of #3731
2026-05-11 23:22:53 +08:00
jinyeandQwen-Coder df90da6f03 feat(telemetry): add sensitive span attribute opt-in (#3893)
* feat(telemetry): add sensitive span attribute opt-in

Add a telemetry setting and environment override for including sensitive attributes in spans created by the log-to-span bridge. Keep the default filtering behavior for prompt, function_args, and response_text unless explicitly enabled.

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

* fix(telemetry): clarify span bridge options

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

* feat(telemetry): populate api response text

Populate response_text on API response telemetry events for non-internal prompts so opted-in bridge spans can include model response bodies.

Exclude thought text from the recorded response text and keep internal prompt responses omitted.

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

* docs(telemetry): clarify sensitive span attribute scope

Clarify that the sensitive span attribute setting only controls log-to-span bridge spans, while response text may still reach other telemetry sinks from API response events.

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

* fix(telemetry): cap recorded response text

Limit response_text captured for API response telemetry to a bounded length and mark truncated values to avoid oversized OTLP attributes.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-05-08 00:36:08 +08:00
jinyeandQwen-Coder 5d1052a358 feat(telemetry): define HTTP OTLP endpoint behavior and signal routing (#3779)
* feat(telemetry): define HTTP OTLP endpoint behavior and signal routing

- Add resolveHttpOtlpUrl() that appends /v1/traces, /v1/logs, /v1/metrics
  to base HTTP OTLP endpoints per the OpenTelemetry specification
- Add per-signal endpoint overrides (otlpTracesEndpoint, otlpLogsEndpoint,
  otlpMetricsEndpoint) for backends with non-standard paths (e.g. Alibaba Cloud)
- Add LogToSpanProcessor that bridges OTel log records to spans for
  traces-only backends, with session-based traceId correlation and
  error status propagation
- Auto-wire LogToSpanProcessor when traces URL exists but logs URL doesn't
- Validate per-signal URLs gracefully (log error + skip, don't crash)
- Preserve query strings when appending signal paths to URLs
- Guard gRPC branch against missing base endpoint with per-signal config
- Update telemetry documentation with signal routing semantics and
  Alibaba Cloud HTTP per-signal endpoint examples

Closes #3734

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

* fix(telemetry): fix TS noPropertyAccessFromIndexSignature errors in tests

Use typed ExportedSpan interface and bracket notation for index signature
properties to satisfy strict TypeScript checks in CI.

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

* fix(telemetry): replace MD5 with SHA-256 for traceId derivation

CodeQL flagged MD5 as a weak cryptographic algorithm when used with
session.id (considered sensitive data). Switch to SHA-256 truncated
to 32 hex chars to satisfy CodeQL while maintaining the same traceId
format required by the OTel specification.

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

* fix(telemetry): address review feedback for LogToSpanProcessor robustness

- Wrap JSON.stringify in try/catch to handle circular refs and BigInt
- Add export timeout (30s) and try/catch to prevent hung shutdown
- Track in-flight exports to avoid interval-vs-shutdown race condition
- Fix deriveSpanStatus: use truthy checks (!!), drop success===false
  heuristic since declined tool calls are normal, not errors
- Enforce http(s) scheme in validateUrl to reject file:/javascript: URLs
- Change DiagLogLevel from ERROR to WARN to preserve operational diagnostics
- Preserve logRecord.instrumentationScope instead of hardcoding
- Forward severityNumber/severityText as span attributes
- Add tests for circular refs, error status edge cases, severity

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

* fix(telemetry): flush sdk shutdown through cleanup

Remove async process exit handlers from telemetry initialization and route SDK shutdown through Config cleanup so normal CLI exit paths await pending telemetry exports. Keep shutdown idempotent while an SDK shutdown is in flight.

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

* fix(telemetry): harden bridged log shutdown

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

* fix(telemetry): address review follow-ups

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

---------
Co-authored-by: Qwen-Coder <[email protected]>
2026-05-01 22:47:01 +08:00
tanzhenxin 6c71b6b09c chore(core): drop tool token usage tracking (#3727)
The `tool_token_count` field was sourced from `toolUsePromptTokenCount`
on the GenAI usage metadata, but none of the providers we adapt
(OpenAI/DashScope, Anthropic) populate it, and Google's Gemini API only
emits it for built-in server-side tools that qwen-code does not use.
The metric was therefore always zero in practice, so the dedicated
counter, telemetry field, UI row, and supporting plumbing are removed
end-to-end (telemetry types, OTEL counter type, UI aggregation, model
stats display, qwen-logger payload, VS Code session schema, and docs).
2026-04-30 15:35:01 +08:00
4be0234d10 docs(telemetry): clarify Alibaba Cloud console entry (#3498)
* docs(telemetry): clarify Alibaba Cloud console entry

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

* docs(telemetry): fix unreachable intl console URL and split new/legacy console guidance

- Replace unreachable tracing-sgnew.console.alibabacloud.com with the
  verified arms.console.alibabacloud.com for international users
- Separate OTLP endpoint retrieval steps by console version: new console
  uses Integration Center, legacy console uses Cluster Configurations →
  Access point information

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* docs(telemetry): align target example with current implementation

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

* docs(telemetry): clarify Alibaba Cloud OTLP setup

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

* docs(telemetry): remove stale TOC entry

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

---------

Co-authored-by: jinye.djy <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-04-26 07:40:35 +08:00
tanzhenxinandQwen-Coder 6e0cf6541d refactor(telemetry): update session event fields to match current config
- Remove deprecated fields: embedding_model, api_key_enabled, vertex_ai_enabled, log_prompts_enabled
- Add new fields: truncate_tool_output_threshold, truncate_tool_output_lines, hooks, ide_enabled, interactive_shell_enabled

This aligns telemetry data with the current CLI configuration options.

Co-authored-by: Qwen-Coder <[email protected]>
2026-03-15 12:06:01 +08:00
tanzhenxin 35c865968f chore: remove tiktoken dependency and replace with character-based token estimation 2026-01-17 22:39:18 +08:00
pomelo-nwu 5742a69d8a feat: update docs 2025-12-15 19:40:14 +08:00