Commit graph

231 commits

Author SHA1 Message Date
qwen-code-ci-bot
40ed6b21d7
chore(release): v0.19.9 (#6693)
* chore(release): v0.19.9

* docs(changelog): sync for v0.19.9

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-11 00:35:15 +00:00
tanzhenxin
7a9ee09f49
fix(core): honor NO_PROXY for model requests (#6640) 2026-07-10 10:41:04 +00:00
qwen-code-ci-bot
b330ec884f
chore(release): v0.19.8 (#6549)
* chore(release): v0.19.8

* docs(changelog): sync for v0.19.8

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-08 15:51:03 +00:00
qwen-code-ci-bot
86ae16a6d6
chore(release): v0.19.7 (#6484)
* chore(release): v0.19.7

* docs(changelog): sync for v0.19.7

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-07 17:25:48 +00:00
qwen-code-ci-bot
4e3fd29781
chore(release): v0.19.6 (#6280)
* chore(release): v0.19.6

* docs(changelog): sync for v0.19.6

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-03 16:37:54 +00:00
qwen-code-ci-bot
2126474c28
chore(release): v0.19.5 (#6194)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
* chore(release): v0.19.5

* docs(changelog): sync for v0.19.5

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-02 13:38:46 +00:00
qwen-code-ci-bot
f3ea17bf43
chore(release): v0.19.4 (#6132)
* chore(release): v0.19.4

* docs(changelog): sync for v0.19.4

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-01 13:13:47 +00:00
Gaurav
fc184f20e5
fix(deps): clear critical runtime audit findings (#6065)
* fix(deps): clear critical runtime audit findings

* fix(core): allow intentional worktree hooks path setup
2026-07-01 05:28:32 +00:00
qwen-code-ci-bot
8b65a555a0
chore(release): v0.19.3 (#5952)
* chore(release): v0.19.3

* docs(changelog): sync for v0.19.3

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-28 06:59:17 +00:00
qwen-code-ci-bot
5bb79bc67c
chore(release): v0.19.2 (#5830)
* chore(release): v0.19.2

* docs(changelog): sync for v0.19.2

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-24 15:00:58 +00:00
qwen-code-ci-bot
8eb5770812
chore(release): v0.19.1 [skip ci]
* chore(release): v0.19.1

* docs(changelog): sync for v0.19.1

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-23 17:26:10 +08:00
qwen-code-ci-bot
57156522bd
chore(release): v0.19.0 [skip ci]
* chore(release): v0.19.0

* docs(changelog): sync for v0.19.0

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-23 11:38:15 +08:00
qwen-code-ci-bot
8f8ed0d7c1
chore(release): v0.18.5 [skip ci]
* chore(release): v0.18.5

* docs(changelog): sync for v0.18.5

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-21 21:13:19 +08:00
qwen-code-ci-bot
c5fb75b5c2
chore(release): v0.18.4 [skip ci]
* chore(release): v0.18.4

* docs(changelog): sync for v0.18.4

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-21 03:36:38 +00:00
qwen-code-ci-bot
45220c219c
chore(release): v0.18.3 [skip ci]
* chore(release): v0.18.3

* docs(changelog): sync for v0.18.3

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-18 00:28:48 +08:00
qwen-code-ci-bot
5e9dc3e181
chore(release): v0.18.2 [skip ci]
* chore(release): v0.18.2

* docs(changelog): sync for v0.18.2

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-17 19:59:27 +08:00
qwen-code-ci-bot
18a0120242
chore(release): v0.18.1 [skip ci]
* chore(release): v0.18.1

* docs(changelog): sync for v0.18.1

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-15 21:43:22 +08:00
qwen-code-ci-bot
e66281590d
chore(release): v0.18.0 [skip ci]
* chore(release): v0.18.0

* docs(changelog): sync for v0.18.0

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-12 22:59:56 +08:00
Yufeng He
adbdff8b1f
fix(cli): avoid headless browser open crashes (#4716)
* fix(cli): avoid headless browser open crashes

* fix(cli): reuse browser launch guard

* test(core): isolate browser launcher env

* fix(core): make browser env launches non-blocking

* fix(core): handle LSP stdio write failures
2026-06-12 13:58:35 +08:00
tanzhenxin
afd631335d
feat: add Agent Team experimental feature for parallel sub-agent coordination (#4844)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
* feat(core): add Agent Team foundation (experimental, flag-gated)

First stage of re-porting the Agent Team feature (originally PR #2886) onto
current main. The branch had diverged 362 commits behind a parallel rewrite
of the agent runtime, so the feature is being re-applied stage by stage
rather than merged.

This stage lands the self-contained agents/team/ subsystem (TeamManager,
mailbox, identity, tasks, leader permission bridge, test-utils) plus the
team_create/team_delete and task_create/task_update/task_list tools, with
the additive plumbing they need:

- Config: TeamManager/TeamContext accessors, cleanupTeamRuntime, and
  isAgentTeamEnabled (settings or QWEN_CODE_ENABLE_AGENT_TEAM=1).
- Tool registry: team/task tools registered lazily, gated on the flag.
- Runtime hooks: completeOnIdle for one-shot teammates; args on the
  agent approval event; teammate-aware tool exclusion sets.
- Backend types: TeamAgentHandle, optional getAgent, completeOnIdle.
- New experimental.agentTeam setting.

Everything is gated behind the experimental flag and inert by default.
Build is green; team unit tests and all touched-file regressions pass.

* feat(core): add send_message team routing (experimental)

Stage 1 of the Agent Team re-port. Extends the send_message tool so it can
route to a teammate (or "*" for broadcast) via TeamManager in addition to
its existing background-task path, and supports the shutdown_request control
message (leader-only). Recipient selection is a oneOf over `to`/`task_id`.

Layered on top of main's classifier integration: send_message keeps its
'ask' default permission and forwards the routing fields + message to the
AUTO classifier, since the message is an instruction the recipient executes.

Re-adds the team-lifecycle E2E test, which now passes end to end
(create -> tasks -> messages -> list -> update -> delete).

* feat(core): let the Agent tool spawn named teammates (experimental)

Stage 3 of the Agent Team re-port. Adds the `name` parameter to the Agent
tool: when a team is active and a name is given, the call routes through
TeamManager.spawnTeammate instead of launching a one-shot subagent. Without
a team the call is rejected up front rather than silently falling back. The
tool description advertises team coordination only when the experimental
flag is on. Ported onto main's rewritten Agent tool.

* feat(cli): render team_result/task_list tool displays (experimental)

Stage 5 (partial) of the Agent Team re-port. Teaches the ToolMessage
result renderer about the TeamResultDisplay and TaskListResultDisplay
shapes so the team/task tools' output is shown via their returnDisplay
text instead of a stringified object, and adds a JSON.stringify safeguard
for any other non-string display object.

The remaining CLI wiring (nonInteractiveCli + useGeminiStream team
drivers, permissionController.handleTeammateApproval) is coupled to the
turn-loop Teammate handling and will land together with Stage 4.

* feat(core): treat teammate messages as top-level turns (experimental)

Stage 4 of the Agent Team re-port. Adds SendMessageType.Teammate and
includes it in isTopLevelInteraction so that a teammate message delivered
to the leader resets the loop detector and opens an interaction span, the
same as a user/cron/notification turn.

Per the agreed minimal integration, teammate turns deliberately do NOT run
the UserQuery/Cron block — they don't bump commit attribution, aren't
recorded as user messages, and don't trigger auto-memory prefetch. That
keeps the edit to main's restructured turn loop to a single condition.

* feat(cli): drive teammates from the headless run loop (experimental)

Stage 5 of the Agent Team re-port. Wires the non-interactive/headless run
loop to the active team: it subscribes to TeamManager changes, drains
teammate messages into the leader's conversation as SendMessageType.Teammate
turns, waits for teammate activity when the leader has no pending tool calls,
and routes teammate tool-approval requests through the session's permission
channel (SDK in stream-json mode; YOLO/cancel fallback otherwise).

Adds PermissionController.handleTeammateApproval and exposes it on the
ControlService permission facade. Ported onto main's restructured run loop
(which added its own cron/notification drain mechanism).

* feat(cli): drive teammates from the interactive turn loop (experimental)

Final Stage 5 piece of the Agent Team re-port. Wires the interactive (TUI)
useGeminiStream hook to the active team: it subscribes to TeamManager,
queues teammate messages, and drains them into the conversation as
SendMessageType.Teammate turns when idle, guarded against racing the
notification drain. Treats teammate turns like user/cron for image-format
checks and new-prompt stats. Ported onto main's rewritten hook.

* fix(core): declare proper-lockfile dependency for the team subsystem

The Agent Team mailbox and task files import proper-lockfile, but the
dependency was never declared in package.json, so a clean `npm ci` (as CI
runs) failed to resolve the module — cascading into implicit-any and
possibly-undefined errors in the same files. It built locally only because
the working tree's node_modules already had the package from an earlier
install.

Adds proper-lockfile to packages/core dependencies and @types/proper-lockfile
to root devDependencies (matching the original feature branch), and
regenerates the lockfile. Build and typecheck are clean.

* fix(team): address review findings on the agent-team subsystem (experimental)

Triaged the unresolved review threads from the superseded PR #2886 against
the re-ported code and applied the valid fixes:

- task_update(status:'deleted') now enforces the same ownership guard as
  updateTask, so a teammate cannot delete another teammate's task.
- Completing a task and adding a blocks edge in the same call no longer
  leaves the dependent permanently blocked by the just-completed task.
- listTasks treats a momentarily-empty (mid-create) task file as a create
  in flight and skips it instead of quarantining and losing the task.
- Fire-and-forget coordination calls (flush, auto-claim, unassign, poll)
  log rejections instead of surfacing as unhandled rejections.
- pollLeaderInbox re-checks the leader callback after the awaited read so a
  detach during the read cannot throw or drop the batch.
- scanIdleAgentsForTasks skips teammates with a pending shutdown.
- broadcast uses allSettled so one terminated recipient does not fail the
  whole broadcast.
- Hybrid tool-response+teammate turns reset the loop detector, preventing a
  false LoopDetected when a polling leader merges teammate messages.
- useGeminiStream drains its teammate queue on a manager swap; the join
  event carries the teammate model for the UI tab label.
- Removed dead consumeUnreadByType and an unreachable ENOENT branch.

Verified: core unit tests (incl. new regressions for the delete guard, the
complete+addBlocks re-block, and the empty-file create race) plus live L3
(3-agent) and L4 (4-agent) E2E, both clean.

* fix(core): close ownership TOCTOU and lock-ordering hazard in agent-team tasks

deleteTask checked ownership against a pre-lock read, so a concurrent
claimTask/updateTask could reassign the owner between the check and the
unlink — silently destroying another teammate's task. Acquire the lock
first, then re-read and re-check ownership inside it before unlinking,
mirroring updateTask. Reciprocal edge cleanup now runs after the lock is
released (never holding two per-task locks at once) but before the single
tasks-updated notification, so no listener observes a phantom blocker.

blockTask issued its two updateTask writes via Promise.all; two calls over
the same pair in opposite directions could deadlock on per-task locks.
Serialize the writes to remove the lock-ordering hazard.

* fix(core): harden agent-team message handling and auto-claim

- Cap per-agent pending messages (MAX_PENDING_MESSAGES). The queue only
  drains when its recipient goes IDLE, so an unbounded queue let a single
  looping teammate balloon a busy teammate's memory; sendMessage now
  applies backpressure once the cap is reached.
- Wrap auto-claimed task content (subject/description, authored by another
  agent) in a <task_content> envelope with a defensive instruction so it
  is treated as data, not as instructions to obey.
- Surface fire-and-forget coordination failures (flush, auto-claim,
  unassign) to the leader's conversation. They were only logged via a
  namespaced debug logger, i.e. invisible in production, despite mapping
  to silent stuck-teammate / stuck-task symptoms.

* fix(core): require approval for agent-team task_create/task_update

A task's subject/description becomes the prompt an idle teammate
auto-claims and executes with full tool access — the same privileged-sink
shape as send_message. Both tools inherited the base default 'allow',
which short-circuits the classifier in AUTO mode. Override
getDefaultPermission to 'ask' so that injection path stays under the
classifier / human-in-the-loop, matching send_message.

* docs(core): correct completeOnIdle JSDoc for team teammates

The JSDoc cited team teammates as the use-case for completeOnIdle:true,
but teammates set it to false so they settle to IDLE (not COMPLETED) and
stay alive for follow-up messages and auto-claim. Document the actual
semantics and the invariant the leader's wait loop relies on.

* fix(core): harden agent-team leader callback and task envelope

- fireAndForget: wrap leaderMessageCallback in try/catch so a throwing
  callback cannot re-introduce the unhandled rejection the wrapper exists
  to prevent (enforces the documented 'must not throw from this catch').
- tryAutoClaimTask: nonce-tag the <task_content> envelope with the
  per-session envelopeNonce (same pattern as formatLeaderEnvelope) so a
  teammate-authored description cannot forge the closing tag and break
  out of the protected zone via a </task_content> payload.

* fix(core): make deleteTask edge cleanup resilient to partial failure

Use Promise.allSettled (was Promise.all) for post-unlink edge cleanup so
a single failing dependent (corrupt JSON, EACCES, lock exhaustion) no
longer skips notifyTasksUpdated for the dependents that were cleaned.
Without this their blockedBy is cleared but scanIdleAgentsForTasks never
re-runs, leaving them stuck idle with no recovery (the task file is
already unlinked, so a retry returns false). Per-failure warnings are
logged.

* fix(cli): mount useTeamInProcess so teammate tabs render

The hook bridging team TEAMMATE_JOINED events to agent-tab registration
(useTeamInProcess) was authored but never mounted in AgentViewProvider —
only useArenaInProcess was. As a result teammate tabs never registered and
the teammate tab bar never appeared during in-process team runs.

Mount useTeamInProcess alongside useArenaInProcess, and label teammate tabs
by name rather than model (teammates inherit the leader's model, so a model
label collapses to a generic "teammate" and is identical across the team).
Add a regression test asserting the provider mounts the team bridge.

* test(terminal-capture): add agent-team feature demo + capture fixes

Add a standalone streaming demo of the agent-team feature that captures the
full lifecycle and the teammate tab navigation into a single GIF
(scenarios/agent-team-demo.ts).

Supporting engine fixes:
- capture(): scroll the xterm viewport to the live bottom before
  screenshotting, so a capture taken after an idle period shows the current
  state instead of stale top-of-buffer scrollback.
- scenario-runner: skip scenarios/*.ts files with no default export (driver
  scripts that guard their own entrypoint), so batch runs don't choke.

* fix(core): serialize in-process mailbox writers to fix Windows lock flakiness

The concurrent-write test fired 10 writeMessage() calls at one inbox,
each contending for the same proper-lockfile lock with a fixed,
non-randomized backoff. On Windows, slower fs syscalls let the tail
writers exhaust the retry budget before winning the lock, throwing
ELOCKED ("Lock file is already being held") — a flaky failure that
alternated pass/fail across CI runs.

Add a per-inbox in-process Mutex (async-mutex, the pattern already used
in jsonl-utils and writeContextFile) so same-process writers serialize
in memory and only one reaches for the file lock at a time. The
proper-lockfile lock stays inside the mutex to preserve cross-process
safety between agent processes. Also randomize the lock backoff to
de-synchronize genuine cross-process contenders.

* feat(team): render teammate reports as a compact notification line

A teammate's report was injected into the leader's conversation as a
raw <teammate_message_<nonce>> envelope and rendered verbatim as a user
bubble — a large, scaffolding-heavy block on screen for what is often
the biggest payload in the feature.

Adopt the two-text split the notification queue already uses: the full
nonce-tagged envelope still goes to the leader's model, but the user now
sees a compact "● <name> reported back" line in its place. The verbatim
USER bubble is suppressed for SendMessageType.Teammate exactly as it is
for Cron, and coordination-error notices get the same treatment.

The leader callback now delivers both the model text and a display
string built in TeamManager (where the structured sender/summary live),
so the UI never parses the envelope. Headless is unchanged — it ignores
the extra arg.

* fix(terminal-capture): widen agent-team-demo Phase C budget so the GIF doesn't cut off

The leader sits idle (no Main-view output) while teammates read their
files, so Phase C captures no frames until a report lands — making
maxPolls the real wall-clock budget. At 80 polls (~112s) a slow second
scout could exhaust it before reporting, ending the GIF mid-run. Bump to
200 polls (~5min) so the capture outlasts the slowest scout plus the
combined summary and delete; the loop still exits early on `deleted` and
idle polls capture no frames, so the GIF doesn't bloat.

* fix(core): separate task-content nonce; forward send_message summary

Address two review findings on the agent-team messaging path:

- The <task_content_…> envelope reused envelopeNonce — the per-session
  nonce the leader trusts to authenticate <teammate_message_…> blocks.
  Because the task-content prompt is delivered to the claiming teammate,
  a teammate could learn the nonce and forge a leader-trusted envelope.
  Use a dedicated taskContentNonce so the leader-trust nonce stays secret
  from teammates.

- The SendMessage 'summary' param was dropped between the tool and the
  mailbox, so the leader UI always showed the '{name} reported back'
  fallback. Thread summary through sendMessage → writeMessage so it
  reaches formatLeaderDisplay.

Adds regression tests for both.

* fix(core,cli): harden agent-team messaging per review round 4

- task-content envelope uses a fresh per-claim nonce instead of a shared
  per-session one, so a teammate that learns one task's nonce can't forge
  a later task's closing tag to inject the next claimant.
- team_delete wraps manager.cleanup() in try/catch and always resets the
  Config team state, so a cleanup failure no longer permanently wedges
  team_create for the rest of the session.
- unassignTeammateTasks uses Promise.allSettled so one corrupt/locked task
  file no longer strands the remaining tasks on a terminated teammate; the
  caller's re-scan still fires.
- non-interactive teammate-approval responses .catch() rejections to avoid
  an unhandledRejection if the teammate terminates mid-approval.
- setupEventBridge warns when the backend can't provide an agent handle or
  event emitter instead of returning silently.

* fix(core): don't let a failed dependent unblock abort task completion

unblockDependents used Promise.all, so a single dependent failing
(corrupt JSON, EACCES, lock exhaustion) rejected out of updateTask
before the completed status was persisted — the task stayed
in_progress on disk while already-processed dependents were
unblocked, leaving the dependency graph inconsistent. Switch to
Promise.allSettled with a debug warning per failure, mirroring the
best-effort edge cleanup in deleteTask and unassignTeammateTasks.

* fix(core): quarantine corrupt teammate inboxes; skip task scan when no agent is idle

Review round 7. A corrupt teammate inbox previously made every
writeMessage/consumeUnread re-throw on the same file, so the teammate
could never receive another message (including shutdown requests) —
while the leader inbox already self-healed via quarantine. readInboxRaw
now renames the corrupt file to .corrupt-{ts} and continues on a fresh
inbox; the leader-side offset clamps to 0 if the inbox shrank behind
the poller so messages are re-surfaced rather than silently skipped.

scanIdleAgentsForTasks now checks for idle members before reading the
task board, avoiding a full tasks-directory scan on every task update
while all agents are busy. Also document the restrictsOwnership field
enumeration hazard and the intentional metadata/activeForm exclusion.

* fix(core): re-check task ownership under the lock when unassigning a terminated teammate

Review round 8. unassignTeammateTasks snapshotted in_progress tasks
and then blind-wrote {status: pending, owner: null} per task, so a
leader reassignment (or the dying teammate's final completion) landing
between the snapshot and the per-task lock was silently reverted.
Releases now go through an in-lock compare-and-set that skips the task
when its owner or status no longer matches the snapshot.

Also isolate task-update listeners (one throwing listener no longer
starves the rest) and drop the lone const enum for subsystem
consistency.

* fix(core): harden team task file layer against partial writes and transient I/O

- createTask claims the ID with an empty O_EXCL placeholder and fills
  it via temp-file + rename, so concurrent readers never see partial
  JSON (which the quarantine would have destroyed mid-create)
- listTasks quarantines only on parse failures; transient read errors
  (EMFILE/EIO/EACCES) skip the file for one round instead of renaming
  a healthy task away, and the read fan-out is capped at 16
- updateTask / claimTask / releaseOwnedTask guard the in-lock readFile
  against ENOENT (resetTaskList and the quarantine rename run without
  per-task locks), mirroring deleteTask
- cover releaseOwnedTask's three defensive branches with tests

* fix(core): drain messages enqueued during the IDLE transition; settle abort on idle agents

- a message enqueued from inside the synchronous IDLE STATUS_CHANGE
  emit (TeamManager's flush) landed after the run loop's final empty
  check while `processing` was still true — enqueueMessage would not
  restart the loop and the message stranded in a dead queue; the loop
  now re-checks the queue after `processing` flips false
- abort() on an idle/initializing agent only set the signal: no loop
  was running to observe it, so the agent never reached a terminal
  status and allTeammatesTerminated()-style gates never fired; abort
  now settles CANCELLED directly when no loop is in flight
- regression tests drive the real AgentInteractive (stub model, real
  loop) through send-during-idle-emit and abort-while-idle

* test(core): align FakeAgent queue and abort semantics with AgentInteractive

FakeAgent modeled a friendlier runtime than the one that ships:
enqueueMessage processed inline (no queue, no processing flag,
resurrecting terminal agents via unconditional RUNNING), which is
exactly what masked the flush-into-dead-queue bug. It now queues
while a round is in flight, drains before settling IDLE, drops
messages after abort()/shutdown() like the real drained queue, and
never resurrects a terminal agent.

* fix(core): surface spawn failures, handle shutdown_rejected, envelope peer messages

- spawnTeammate now checks the agent's status after spawnAgent
  resolves: start() reports chat-creation failure via FAILED without
  throwing, so the leader was told the teammate joined while sends
  were accepted into a queue that could never flush; a failed spawn
  now rolls back and surfaces the reason (with a terminal-status
  replay in setupEventBridge for the attach race)
- shutdown_rejected now clears _shutdownPending: a teammate that
  declined once stayed excluded from auto-claim and kill-armed on any
  later "shutdown_approved" mention
- peer-to-peer deliveries get a fresh-nonce envelope like leader
  deliveries, closing inline leader impersonation between teammates
  (deliberately not the leader-trust nonce, which must never reach
  teammate context)

* fix(core): exclude workflow tool from teammates

The teammate ALS identity propagates into anything a teammate spawns,
so prepareTools() keeps choosing the teammate exclusion set for nested
agents — without WORKFLOW in it, a teammate-launched workflow re-arms
the O(k^n) recursive fan-out the subagent exclusion set prevents.

* fix(core): make task tools visible to permission review; reject dependency cycles

- task_create / task_update now project their content (subject,
  description, status, owner, edges) to the AUTO classifier — the base
  '' sentinel projected to an empty object, so the classifier ruled on
  task_create({}) and the 'ask' override was blind; the interactive
  confirmation now shows the description (truncated), since that text
  is what a claiming teammate executes
- task_update rejects self-edges and dependency cycles instead of
  silently persisting a graph that auto-claim can never unblock
- regression tests pin the 'ask' default and a non-empty classifier
  projection for both tools

* fix(core): reclaim stale teams on team_create instead of wedging the name

Nothing deletes team dirs on normal exit (only an explicit team_delete
does), so every Ctrl+C, completed headless run, or crash permanently
wedged the team name behind createTeamFile's wx-exclusive create, with
manual rm -rf as the default recovery. team_create now records the
owner identity (leadSessionId + leadPid) and, on EEXIST, reclaims the
team when the recorded lead process is gone (or is this process);
only a live concurrent owner keeps the name refused.

* fix(cli): pass teammate envelopes straight to the model, skipping shell/@/slash preprocessing

Teammate envelopes are model-authored text already rendered as a
notification line by the teammate drain, but they still flowed through
the user-input preprocessing: with shell mode active a teammate report
was EXECUTED as a shell command, and a leading / or an @path was
reinterpreted against the leader's session. They now early-return like
Notification.

* fix(core): exclude Teammate from UserPromptSubmit hooks and record it in chat history

Teammate envelopes are machine-driven re-entries like Cron and
Notification: user-authored UserPromptSubmit hooks must not fire on
(or block) internal coordination traffic. They also never reached any
chat-recording path — record them like notifications so a resumed
session restores the same compact info line the live UI rendered.

* fix(cli): stop teammate-approval rejections from escaping as unhandled rejections

The stream-json listener voided handleTeammateApproval's promise while
the handler's own error path re-issues a respond() that can reject
(teammate terminated mid-request) — an unhandledRejection that can take
down an SDK session. The call site now catches like its headless
siblings, and the controller's catch-path respond(Cancel) is wrapped so
the method never rejects out of its own error path.

* test(core): add getSessionId to team-lifecycle mock config
2026-06-11 07:58:32 +08:00
易良
d7327df399
fix(skills): use full YAML parser for frontmatter to support block scalars (#4870)
* fix(skills): use full YAML parser for frontmatter to support block scalars

The custom `yaml-parser.ts` does not handle YAML block scalar syntax
(`>` folded, `|` literal). When a SKILL.md uses `description: >` with
indented continuation lines, only the `>` character is captured as the
description value.

Switch both skill loading paths (`skill-load.ts` and `skill-manager.ts`)
to use the `yaml` npm package (already installed for hooks parsing) as
the primary parser, with a fallback to the simple parser for malformed
YAML that the strict parser rejects.

This also simplifies `skill-manager.ts` by removing the special-case
re-parse for `hooks:` — the full parser now handles all fields uniformly.

Closes #4869

* fix(skills): address review feedback — explicit dep, null guard, shared parser

- Add `yaml` to `packages/core/package.json` explicit dependencies
- Move the full-YAML-with-fallback logic into `yaml-parser.ts` as the
  single `parse()` entry point, eliminating the duplicated wrapper
- Add null/type guard so `yaml.parse('')` (returns null) falls back
  cleanly instead of throwing TypeError
- Add debug logging when the full parser fails and fallback triggers
- Tighten `|` block scalar test to use `toBe` instead of `toContain`
- Move block scalar tests to `yaml-parser.test.ts` (no mock interference)
- Add `>-` strip chomping test

* chore: make parseSimple private, remove stale yaml-dep comment

- `parseSimple` is an internal fallback, no external caller needs it —
  stop exporting it via `index.ts`'s `export *`
- Remove the outdated "yaml package would need to be added" comment in
  `subagent-manager.ts` since `yaml` is now an explicit dependency

* test: add comprehensive edge-case tests, use YAML 1.2 core schema

- Use `schema: 'core'` to prevent date-like strings (e.g. `2024-01-01`)
  from being coerced into Date objects, which would break parse→stringify
  roundtrips in claude-converter and subagent-manager
- Add yaml-parser unit tests for: Date non-coercion, bare keys returning
  null, explicit null/tilde, yes/no as strings (YAML 1.2), empty input,
  comment-only input, malformed YAML fallback
- Expand skill-load real-parser integration tests: literal block scalar,
  strip-chomped folded, allowedTools array, complex mixed frontmatter,
  malformed YAML graceful fallback

* fix: use loose null check for optional frontmatter fields

`yaml.parse` returns `null` for bare keys (`hooks:` with no value),
while the old simple parser returned `''`. The existing `!== undefined`
guards don't catch null, which would cause a TypeError in
parseHooksConfig and a misleading error for allowedTools.

Change to `!= null` (catches both null and undefined) in both
skill-load.ts and skill-manager.ts.

* refactor: normalize null values in yaml parser instead of caller-side guards

Move the null→undefined normalization into `parse()` via `stripNullValues`
so that callers keep using strict `!== undefined` checks. This avoids the
loose `!= null` pattern and fixes the contract for all five call sites
(skill-load, skill-manager, subagent-manager, claude-converter,
rulesDiscovery) in one place.

* fix(yaml-parser): address review round 2 — security hardening and dedup

- Prevent prototype pollution via Object.create(null) in both
  stripNullValues and parseSimple (fixes __proto__ injection vector)
- Recursively sanitize nested objects (Date/Uint8Array/null/__proto__)
- Upgrade fallback/non-object log level from debug to warn
- Filter !!timestamp/!!binary explicit tags (belt-and-suspenders)
- Extract shared parseAllowedToolsField helper to deduplicate
  skill-load.ts and skill-manager.ts
- Add runtime type guard for hooksRaw in skill-manager.ts
- Fix fallback tests to use genuinely invalid YAML input

* fix(yaml-parser): apply stripNullValues to parseSimple fallback path

The fallback path returned parseSimple() output directly, bypassing
stripNullValues(). This caused inconsistent output: the main path
stripped null values so callers could use `!== undefined`, but the
fallback preserved them. Wrap the fallback return in stripNullValues()
and add a test verifying null-stripping consistency across both paths.
2026-06-10 21:12:52 +08:00
qwen-code-ci-bot
16dd99fa3c
chore(release): v0.17.1 [skip ci]
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-03 22:03:25 +08:00
InfiniteUselessness
d8add21526
chore(deps): update @google/genai from 1.30.0 to 2.6.0 (#4485)
Bundles three dependency updates:
- @google/genai 1.30.0 -> 2.6.0 - adapts finish-reason mapping to new enum values (RECITATION/BLOCKLIST/PROHIBITED_CONTENT/SPII/IMAGE_* -> content_filter)
- @modelcontextprotocol/sdk 1.25.1 -> 1.25.2
- @types/react and @types/react-dom ^18 -> ^19 (web-templates devDeps)

Smoke-tested against real 2.6.0: build + esbuild bundle pass; real OpenAI-compatible calls (text generation + tool-calling round-trip) succeed at runtime.
2026-06-03 11:01:15 +08:00
qwen-code-ci-bot
54fc50360c
chore(release): v0.17.0 [skip ci]
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-29 18:02:11 +08:00
qwen-code-ci-bot
efba25f063
chore(release): v0.16.2 [skip ci]
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-29 17:10:07 +08:00
jinye
62ed44e1f3
feat(telemetry): client-side HTTP span + opt-in W3C traceparent propagation (#4384) (#4390)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
* 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/instrumentation-undici@0.14.0` (peer-compatible
with the installed `@opentelemetry/instrumentation@0.203.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/instrumentation@0.203.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
qwen-code-ci-bot
394e2a3fa8
chore(release): v0.16.1 [skip ci]
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-23 23:09:48 +08:00
易良
fc15b3312d
chore(release): v0.16.0 (#4404)
* chore: bump version to 0.16.0 and normalize bat line endings

* revert: restore install-qwen-standalone.bat to original CRLF encoding

The previous bump commit inadvertently normalized line endings from
CRLF to LF. Windows batch files must retain CRLF in the repository
to work correctly with cmd.exe.

* revert: remove spurious NOTICES.txt change from version bump
2026-05-21 19:49:49 +08:00
qwen-code-ci-bot
d419a92672
chore(release): v0.15.11 [skip ci]
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-14 09:51:46 +08:00
ChiGao
cadda23782
chore(deps): upgrade ink 6.2.3 → 7.0.2 + bump Node engine to 22 (#3860)
* chore(deps): upgrade ink 6.2.3 -> 7.0.2 + bump Node engine to 22

ink 7 requires Node >=22 and react-reconciler 0.33 with React >=19.2,
so this PR also bumps:

- Node engines (root + cli + core) 20 -> 22
- React/react-dom 19.1 -> 19.2.4 (pinned exact via overrides to keep
  the transitive React graph deduped to a single instance)
- @types/node pinned to 20.19.1 via overrides to avoid an unrelated
  Dirent NonSharedBuffer regression in sessionService tests
- @vitest/eslint-plugin pinned to 1.3.4 to avoid an unrelated lint
  regression introduced by the 1.6.x rule additions
- react-devtools-core 4.28 -> 6.1 (ink 7 peerOptional requires >=6.1.2)
- ink hoisted to root devDeps so workspace-private peer-dep contention
  doesn't push ink-link/spinner/gradient into nested workspace
  installs (which would skip transitive resolution for terminal-link)

Workflow + image + installer alignment:

- .nvmrc 20 -> 22
- Dockerfile node:20-slim -> node:22-slim
- CI test matrix drops 20.x (keeps 22.x + 24.x)
- terminal-bench workflow Node 20 -> 22
- Linux/Windows install scripts upgrade their Node version targets

Documentation alignment:

- README.md badge + prerequisites
- AGENTS.md, CONTRIBUTING.md, docs/users/quickstart.md,
  docs/users/configuration/settings.md, docs/developers/contributing.md,
  docs/developers/sdk-typescript.md, docs/users/extension/extension-releasing.md,
  packages/sdk-typescript/README.md, packages/zed-extension/README.md,
  scripts/installation/INSTALLATION_GUIDE.md

Test gating:

- Two AuthDialog/AskUserQuestionDialog tests that drive <SelectInput>
  through ink-testing-library now race ink 7's frame-throttled input
  delivery and land on the wrong option. The maintainers had already
  marked one of them unreliable (skip on Win32 + CI+Node20). Extend
  that gate to cover all environments until upstream
  ink-testing-library ships an ink-7-compatible release that flushes
  input deterministically. The other test now uses it.skip with the
  same comment. No business code changes.

Verified locally:

- npm run typecheck across all workspaces: clean
- npm run lint (root): clean
- npm run test --workspaces:
    cli  312/312 files, 4918 passed, 9 skipped
    core 266/266 files, 6836 passed, 3 skipped
    webui 6/6, 201 passed
    sdk  40/40, 283 passed, 1 skipped
- npm ls ink: single ink@7.0.2 instance across all peer deps
- single react@19.2.4 instance

Generated with AI

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

* chore: align Node 22 floor across all shipping artifacts

Reviewer (tanzhenxin) flagged five surfaces where the >=22 engine bump
leaked: SDK package metadata, web-templates engines, /doctor runtime
check, main bundler target, and SDK bundler target. Each was a separate
escape hatch letting Node 18/20 consumers install or run the artifact
on an unsupported runtime.

- packages/sdk-typescript/package.json: engines.node >=18.0.0 -> >=22.0.0
- packages/web-templates/package.json: engines.node >=20 -> >=22
- packages/cli/src/utils/doctorChecks.ts: MIN_NODE_MAJOR 20 -> 22
- esbuild.config.js: target node20 -> node22 (main CLI bundle)
- packages/sdk-typescript/scripts/build.js: target node18 -> node22 (esm + cjs)
- packages/cli/src/utils/doctorChecks.test.ts: rename test label to v22+

Generated with AI

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

* ci(e2e): bump E2E workflow Node matrix to 22.x

Reviewer (tanzhenxin) flagged that e2e.yml still pinned node-version
20.x while root engines is now >=22, so every E2E run on push would
either fail at npm ci with engine error or silently exercise the bundle
on a runtime that's no longer in ci.yml's test matrix.

The macOS job in the same workflow already reads .nvmrc (which is 22)
so this only updates the Linux matrix.

Generated with AI

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

* fix(deps): drop root wrap-ansi override so ink 7 gets its declared dep

Reviewer (tanzhenxin) flagged that the root overrides.wrap-ansi: 9.0.2
predates this upgrade and forces every consumer (including ink) to v9,
while ink 7 declares wrap-ansi: ^10.0.0. The lockfile had no nested
install under node_modules/ink/, so ink 7 was running with a transitive
dep one major below its declared minimum.

Dropping the global override lets ink resolve its own wrap-ansi 10
nested install (now visible in the lockfile under
node_modules/ink/node_modules/wrap-ansi), while the cli package's own
direct `wrap-ansi: 9.0.2` dependency keeps the cli code path
(TableRenderer.tsx) on the version it has been tested against. The
nested cliui override is preserved for yargs which still needs v7.

Verified via `npm ls wrap-ansi`:
- ink@7.0.2 -> wrap-ansi@10.0.0 (newly nested)
- @qwen-code/qwen-code -> wrap-ansi@9.0.2 (unchanged)
- yargs/cliui -> wrap-ansi@7.0.0 (unchanged)

Generated with AI

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

* test(InputPrompt): un-skip placeholder ID reuse after deletion

Reviewer (tanzhenxin) flagged that the new it.skip on the
'should reuse placeholder ID after deletion' test was undisclosed in
the PR description and removed coverage of real product behavior
(freePlaceholderId / bracketed-paste backspace path) without a
TODO(#NNNN) link.

Their argument was sound: the skip rationale pointed at ink 7's input
throttle, but this same file just bumped the wait helper from 50ms to
150ms specifically to give ink 7 frame time. Re-running the test under
the bumped wait shows it passes reliably (5/5 runs in the full-file
context, 9/10 alone), so the skip was masking the throttle-flake that
the wait bump already addresses, not a real product bug.

Drop the it.skip and the now-stale comment so coverage of the
freePlaceholderId reuse logic is restored.

Generated with AI

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

* test(InputPrompt): bump first prompt-suggestion test wait to 350ms

The "accepts and submits the prompt suggestion on Enter when the buffer
is empty" test is the first in its describe block, so it pays the
renderer cold-start cost. On macOS-22.x CI runners that pushes the
Enter → onSubmit microtask past the default 150ms post-Enter wait. Match
the 350ms initial render wait used immediately above to absorb the cold
start.

* Revert "test(InputPrompt): bump first prompt-suggestion test wait to 350ms"

This reverts commit 6add83b62ea80c551c81f54af1fda3e6e7478f55.

* test(InputPrompt): wait for followup suggestion debounce before pressing Enter

Root cause of the failing prompt-suggestion tests on macOS and Windows
CI is not flaky timing of the test post-Enter wait — it's the 300ms
debounce inside createFollowupController.setSuggestion (shared core).
The Enter handler reads followup.state.isVisible synchronously, so if
the debounce timer has not fired before stdin.write('\\r'), the
suggestion path is skipped and onSubmit never runs. No amount of
post-Enter wait can recover from that — the keypress was already
processed against stale state.

The original wait(350) only left ~50ms margin over the 300ms debounce,
which ink 7 / React 19.2 mount overhead consumed on slow Windows
runners. Bump the initial wait to 700ms (named SUGGESTION_VISIBLE_WAIT_MS)
to give the debounce timer + cold-start render a generous buffer.

Apply to the two sibling tests too — without the wait their "does not
accept" assertions pass trivially when suggestion is never visible,
which is a false green that hides regressions in the actual reject path.

* fix(deps): align cli wrap-ansi with ink 7 (9.0.2 -> ^10.0.0)

Ink 7 ships its own wrap-ansi@10. CLI's direct dep was pinned to 9.0.2,
causing two copies of wrap-ansi in node_modules and a potential drift in
CJK width / ANSI handling between ink's internal text wrapping and our
TableRenderer.

Upgrading the CLI's direct dep to ^10.0.0 lets npm dedupe to a single
wrap-ansi@10 used by both ink and TableRenderer. API surface is
identical; the only documented behaviour change is that tabs are
expanded to 8-column tab stops before wrapping, which TableRenderer
doesn't feed in.

TableRenderer test suite (43 tests) passes against wrap-ansi@10.

Generated with AI

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

* chore(deps): document @types/node 20.x pin in overrides

The override pinning @types/node to 20.19.1 (while engines require
Node >=22) is intentional: bumping to @types/node@22.x re-introduces
a Dirent<NonSharedBuffer> type regression that breaks
@qwen-code/qwen-code-core/sessionService tests.

Add a sibling "//@types/node" note inside `overrides` so future
maintainers see the rationale and know when to revisit the pin
without having to dig through PR #3860 history.

Generated with AI

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

* test(AskUserQuestionDialog): link skipped Submit-tab test to tracking issue

The 'shows unanswered questions as (not answered) in Submit tab' test
was switched to `it.skip` in the ink 7 upgrade because
`ink-testing-library@4.0.0` doesn't flush input deterministically
through ink 7's 30fps throttle.

Add a `// TODO(#4036):` marker so the skip is greppable and can be
re-enabled once upstream ships an ink-7-compatible release.

Refs #4036

Generated with AI

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

* fix(deps): move @types/node pin comment out of overrides block

npm's `overrides` field requires every key to be a real package name —
the `"//@types/node"` comment-key added in 205855875 trips Arborist with
"Override without name" and breaks `npm ci` across all CI jobs.

Move the explanation to a sibling top-level `"//overrides"` key, which
npm ignores at the document root. Same documentation value, no
override-parser collateral damage.

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-05-11 17:29:50 +08:00
qwen-code-ci-bot
f261229ad5
chore(release): v0.15.10 [skip ci]
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-10 16:38:09 +08:00
qwen-code-ci-bot
d1a600acc4
chore(release): v0.15.9 [skip ci]
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-08 22:46:11 +08:00
qwen-code-ci-bot
0491252b27
chore(release): v0.15.8 (#3928) [skip ci]
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-08 00:46:00 +08:00
qwen-code-ci-bot
76765b5aa2
chore(release): v0.15.7 (#3907)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-07 18:50:01 +08:00
qwen-code-ci-bot
3f0b47172a
chore(release): v0.15.6 (#3766)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-30 15:59:35 +08:00
易良
8de1bcb279
chore(release): bump version to 0.15.3 (#3708)
Some checks failed
Qwen Code CI / CodeQL (push) Waiting to run
Qwen Code CI / Lint (push) Waiting to run
Qwen Code CI / Test (push) Blocked by required conditions
Qwen Code CI / Test-1 (push) Blocked by required conditions
Qwen Code CI / Test-2 (push) Blocked by required conditions
Qwen Code CI / Test-3 (push) Blocked by required conditions
Qwen Code CI / Test-4 (push) Blocked by required conditions
Qwen Code CI / Test-6 (push) Blocked by required conditions
Qwen Code CI / Test-7 (push) Blocked by required conditions
Qwen Code CI / Test-5 (push) Blocked by required conditions
Qwen Code CI / Test-8 (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
SDK Python / SDK Python (3.10) (push) Has been cancelled
SDK Python / SDK Python (3.11) (push) Has been cancelled
SDK Python / SDK Python (3.12) (push) Has been cancelled
Update all package versions from 0.15.2 to 0.15.3 across the monorepo
including root package.json, package-lock.json, and all sub-packages
(channels, cli, core, vscode-ide-companion, web-templates, webui).

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-28 21:04:52 +08:00
易良
44b482928b
chore(release): bump version to 0.15.2 (#3596)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

Update version from 0.15.1 to 0.15.2 across all packages and lockfile
2026-04-24 19:55:12 +08:00
顾盼
9010c09123
chore: bump version to 0.15.1 (#3541)
Some checks are pending
Qwen Code CI / Lint (push) Waiting to run
Qwen Code CI / Test (push) Blocked by required conditions
Qwen Code CI / Test-1 (push) Blocked by required conditions
Qwen Code CI / Test-2 (push) Blocked by required conditions
Qwen Code CI / Test-3 (push) Blocked by required conditions
Qwen Code CI / Test-4 (push) Blocked by required conditions
Qwen Code CI / Test-5 (push) Blocked by required conditions
Qwen Code CI / Test-6 (push) Blocked by required conditions
Qwen Code CI / Test-7 (push) Blocked by required conditions
Qwen Code CI / Test-8 (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-23 11:06:07 +08:00
易良
f2fac208ff
chore(release): bump version to 0.15.0 (#3526)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

Upgrade all package versions from 0.14.5 to 0.15.0 across the monorepo, including package-lock.json and sandbox image references.
2026-04-22 19:26:13 +08:00
tanzhenxin
17269fa0e6
chore(release): bump version to 0.14.5 (#3298)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-15 22:43:29 +08:00
tanzhenxin
8d34d33246
chore: bump version to 0.14.4 (#3209)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-13 18:15:54 +08:00
pomelo
338c0b1e9e
refactor: merge test-utils package into core (#3200)
* refactor: merge test-utils package into core

Consolidate the standalone @qwen-code/qwen-code-test-utils package
into packages/core/src/test-utils/, eliminating the need for a
separate package that only provided createTmpDir, cleanupTmpDir,
and FileSystemStructure type.

Changes:
- Move file-system-test-helpers.ts into core/src/test-utils/
- Re-export from core's test-utils index
- Update 3 core test files to use relative imports
- Update cli useAtCompletion test to import from @qwen-code/qwen-code-core
- Remove test-utils devDependency from core and cli package.json
- Delete packages/test-utils/ directory

All affected tests pass (fileSearch, crawler, ignore, useAtCompletion).

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

* fix: remove deleted test-utils from build order

The test-utils package was merged into core but the build script still
tried to build it separately, causing CI failures.

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-13 17:11:03 +08:00
易良
55bcec70d0
chore: bump version to 0.14.3 (#3112)
Some checks are pending
Qwen Code CI / Lint (push) Waiting to run
Qwen Code CI / Test (push) Blocked by required conditions
Qwen Code CI / Test-1 (push) Blocked by required conditions
Qwen Code CI / Test-2 (push) Blocked by required conditions
Qwen Code CI / Test-3 (push) Blocked by required conditions
Qwen Code CI / Test-4 (push) Blocked by required conditions
Qwen Code CI / Test-5 (push) Blocked by required conditions
Qwen Code CI / Test-6 (push) Blocked by required conditions
Qwen Code CI / Test-7 (push) Blocked by required conditions
Qwen Code CI / Test-8 (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
2026-04-10 21:08:34 +08:00
易良
f296eb1a6d
fix(release): run release:version 0.14.2 to sync all package versions (#3026)
The previous version bump commit (bb4376c) only updated the root
package.json but did not run `npm run release:version` to propagate
the version and sandboxImageUri to all workspace packages.

This caused Docker sandbox integration tests to fail in CI with
"manifest unknown" because build_sandbox.js built image 0.14.1
(from packages/cli/package.json) while sandboxConfig.ts expected
image 0.14.2 (from root package.json).

Fixes: https://github.com/QwenLM/qwen-code/actions/runs/24135197272/job/70424966323
2026-04-08 21:33:40 +08:00
tanzhenxin
47fac88695 chore: bump version to 0.14.1
Bump version across all packages from 0.14.0 to 0.14.1.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-03 16:17:10 +08:00
tanzhenxin
555f92ff21 chore(release): bump version to 0.14.0
- Update all packages from 0.13.x to 0.14.0
- Update sandbox image URI to 0.14.0

This prepares the 0.14.0 release with updated version numbers
across all workspace packages.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-01 19:23:49 +08:00
tanzhenxin
89b79544d1 fix: upgrade @lydell/node-pty to 1.2.0-beta.10 to fix PTY FD leak
The previous version (1.1.0) has a native-level bug on macOS where each
PTY spawn leaks one /dev/ptmx file descriptor that is never closed. Over
a long session with hundreds of shell commands, this exhausts the
system-wide PTY pool (kern.tty.ptmx_max = 511), breaking other programs
like tmux and new terminal windows.

Root cause: microsoft/node-pty#882

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-01 07:55:56 +08:00
DennisYu07
3fac7f6334 chore: bump version to 0.13.2
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-30 16:21:33 +08:00
qwen-code-ci-bot
070ec5b43e
chore: bump version to v0.13.1 (#2716)
Some checks failed
Qwen Code CI / Lint (push) Has been cancelled
Qwen Code CI / CodeQL (push) Has been cancelled
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Has been cancelled
E2E Tests / E2E Test (Linux) - sandbox:none (push) Has been cancelled
E2E Tests / E2E Test - macOS (push) Has been cancelled
Qwen Code CI / Test (push) Has been cancelled
Qwen Code CI / Test-1 (push) Has been cancelled
Qwen Code CI / Test-2 (push) Has been cancelled
Qwen Code CI / Test-3 (push) Has been cancelled
Qwen Code CI / Test-4 (push) Has been cancelled
Qwen Code CI / Test-5 (push) Has been cancelled
Qwen Code CI / Test-6 (push) Has been cancelled
Qwen Code CI / Test-7 (push) Has been cancelled
Qwen Code CI / Test-8 (push) Has been cancelled
Qwen Code CI / Post Coverage Comment (push) Has been cancelled
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-27 18:32:26 +08:00
LaZzyMan
f9d9a985ce Merge branch 'main' into feat/support-permission 2026-03-19 11:24:30 +08:00