- Changed default channel from 'Desktop' to 'ACP' in AcpProcessClient and related interfaces.
- Updated tests to reflect new channel naming and added support for CLI channel overrides.
- Enhanced error handling in SessionSocketHub to normalize error responses for better clarity.
- Modified session creation and loading responses to include 'cwd' in the session object.
- Added a new test case to surface ACP prompt errors as structured protocol objects.
* feat(vscode): expose /skills as slash command with secondary picker
Add a secondary completion picker for the /skills slash command in the
VSCode IDE companion, allowing users to browse and select skills from
a dropdown before sending.
Changes:
- CLI: add 'skills' to ALLOWED_BUILTIN_COMMANDS_NON_INTERACTIVE whitelist
- CLI: send available_skills_update via ACP with skill names/descriptions
- Extension: handle available_skills_update in session update handler
- Webview: implement secondary picker that triggers after selecting /skills
- Webview: allow spaces in completion trigger for /skills sub-queries
Closes#1562
Made-with: Cursor
* feat(vscode-ide-companion): embed skills in commands update metadata
- Move available skills from separate session update to _meta field of
available_commands_update for more efficient delivery
- Simplify skill data to just skill names (string array)
- Add skillsCompletion utility for secondary picker logic
- Cache available skills in WebViewProvider for replay on webview ready
- Update all related types and handlers to support the new structure
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* refactor(vscode-ide-companion): simplify skills picker flow
* refactor(vscode-ide-companion): extract skills completion utils to shared module
Move `isSkillsSecondaryQuery`, `shouldOpenSkillsSecondaryPicker`, and
`SKILL_ITEM_ID_PREFIX` from App.tsx and useCompletionTrigger.ts into a
shared `completionUtils.ts` file to eliminate duplication.
* fix(vscode-ide-companion): restore skills picker state on reload
Cache and replay available skills when the webview becomes ready again.
Clear stale skills when commands metadata does not include availableSkills.
* fix(vscode-ide-companion): replay slash commands after webview reload
Cache available commands in the webview provider.
Replay them on webviewReady so slash command state survives reloads.
* fix(vscode-ide-companion): import AvailableCommand from ACP SDK
* fix(vscode-ide-companion): fallback /skills to direct command
* test(vscode-ide-companion): cover skills secondary picker flow
* test(vscode-ide-companion): guard App mock initialization
* fix(vscode-ide-companion): remove duplicate AvailableCommand import
The auto-merge introduced a duplicate AvailableCommand in the
@agentclientprotocol/sdk import block, causing TS2300.
* fix(vscode-ide-companion): remove duplicate availableCommands replay in handleWebviewReady
The handleWebviewReady method was sending cachedAvailableCommands twice
on every webview-ready handshake, causing an unnecessary extra state
update in the webview.
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* feat(cli): add Traditional Chinese (zh-TW) as a UI language option
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix: use upstream unused-keys-only-in-locales.json to resolve conflict
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* revert: remove check-i18n.ts changes to avoid pre-existing zh.js issues
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* feat(cli): add Traditional Chinese (zh-TW) as a UI language option
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): add WITTY_LOADING_PHRASES to zh-TW locale
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): sync zh-TW.js with en.js keys, fix double-escape, fix check-i18n.ts
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix: resolve conflict in unused-keys-only-in-locales.json
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): add missing Performance translation to zh-TW
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): add quotes to Performance key in zh-TW
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): regenerate zh-TW.js with correct multi-line value parsing
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix: resolve conflict in unused-keys-only-in-locales.json
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): regenerate zh-TW.js with correct multi-line value parsing
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): standardize zh-TW.js key quoting and sync zh.js keys
- Convert zh-TW.js keys from double-quoted to single-quoted to match en.js style
- Fix zh.js key mismatches: add missing keys (Value:, No server selected, prompts, required, Enum) and remove extra keys (The name of the extension to update, Session (temporary))
- Regenerate unused-keys-only-in-locales.json
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): update loading phrases when UI language changes
Add getCurrentLanguage() to useMemo deps in usePhraseCycler so that
WITTY_LOADING_PHRASES re-evaluates after a /language switch instead of
staying locked to the language active at mount time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(i18n): normalize locale separators and fix case-insensitive language lookup
- detectSystemLanguage(): normalize POSIX locales (e.g. zh_TW.UTF-8 → zh-tw)
by replacing underscores with hyphens and lowercasing before matching, so
users with LANG=zh_TW.UTF-8 correctly detect zh-TW instead of falling
through to zh
- getLanguageNameFromLocale(): compare codes case-insensitively so that
normalizeOutputLanguage('zh-TW') resolves to 'Traditional Chinese' instead
of falling back to 'English'
- Add test cases for zh-TW / zh-tw / ZH-TW in normalizeOutputLanguage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(test): update getLanguageNameFromLocale mock to include zh-TW
Add 'zh-tw' entry to the mock map and normalize locale input with
toLowerCase() so the mock mirrors the real case-insensitive implementation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* perf(core): make chat recording writes async
Every recorded chat event (user message, assistant turn, tool call,
tool result, slash command, etc.) was issuing 4 sync fs syscalls on
the main event loop: existsSync(dir) + mkdirSync(dir) + existsSync(file)
+ appendFileSync(file). For a tool-heavy prompt this added ~88 sync
I/O calls per session, blocking the UI render and keypress handler
during each one.
- chatRecordingService.appendRecord: cache ensure-flags so dir/file
creation runs once per session, then enqueue the actual write on a
per-instance promise chain (writeChain). lastRecordUuid is updated
synchronously so chained createBaseRecord still sees the right
parentUuid without waiting for the previous write.
- chatRecordingService.flush: drains the chain — wired into
Config.shutdown so no records are lost on exit.
- jsonl-utils.writeLine: now actually async (fs.promises.mkdir +
fs.promises.appendFile) with per-dir mkdir cache. The existing
per-file mutex still serializes writes correctly.
- Tests updated to await flush() before assertions.
Trace measurement on a single tool-heavy prompt: 110 → 20 sync I/O
calls (-82%), with chatRecordingService dropping from 88 to 0.
* perf(core): cache repeated fs lookups on tool hot path
Each tool invocation went through validatePath → isPathWithinWorkspace
→ fullyResolvedPath, plus its own existence/dir checks. The same paths
got re-resolved across back-to-back tool calls, and ripGrep re-
discovered .qwenignore on every Grep.
- workspaceContext.fullyResolvedPath: bounded LRU on input path
(1024, FIFO). Failed resolutions are NOT cached so retries work.
- paths.validatePath: cache positive isDirectory results; ENOENT
falls through every time so a freshly created file is picked up
immediately.
- ripGrep: module-level caches for searchPath-is-dir and per-dir
.qwenignore presence (256 each, FIFO).
- fileUtils.processSingleFileContent: drop the existsSync gate;
let fs.promises.stat throw ENOENT and convert to FILE_NOT_FOUND
in catch.
Trace: 20 → 10 sync I/O calls. Cumulative reduction since the
chat-recording change: 110 → 10, -91%. All 6057 core tests pass.
* test(core): cache reset hooks + regression-guards from audit
Self-review pass on the previous two perf commits surfaced a few
follow-ups worth pinning down before they bite:
- Module-level caches (paths.isDirectoryCache, ripGrep dirIsDir/qwen-
Ignore, jsonl-utils.ensuredDirs) persisted across vitest cases
silently. Added underscore-prefixed `_reset*ForTest` exports and
wired one into the validatePath describe block so future cases
mutating the same absolute paths can't pass by accident.
- Documented the parentUuid-chain tradeoff on chatRecordingService
.appendRecord: when the async write rejects, lastRecordUuid was
already set sync, so subsequent records reference an absent
ancestor — readers like sessionService.reconstructHistory then
silently drop those descendants. Same observable failure mode as
the prior sync code's caught-and-logged throw.
- Documented the dir<->file mutation and mid-session .qwenignore
staleness windows for the validatePath / ripGrep caches.
- Added regression tests:
* validatePath does NOT cache ENOENT (Edit-then-Read works)
* validatePath skips re-stat on cache hit (perf assertion)
* flush() resolves immediately on a fresh service
* a rejected writeLine does not block the next record
Full core suite: 6061 pass, 2 skipped — no regressions.
* fix(core): cache chatsDirEnsured only on mkdir success
Pre-fix, the flag flipped to true even when mkdirSync threw, so a single
transient failure (NFS EACCES, sandbox mount race, parent dir briefly
missing) would short-circuit every subsequent appendRecord and silently
drop the rest of the session's transcript with no error surfaced.
Reported by zhangxy-zju on #3581.
* fix(cli): destroy stdout instead of process.exit on EPIPE
Routine CLI patterns like `qwen -p ... | head -1` / `| less` / `| grep -m1`
close the downstream pipe and trigger EPIPE. The previous handler called
process.exit(0), which bypassed the caller's runExitCleanup -> Config
.shutdown -> chat-recording flush() chain and silently dropped queued
JSONL writes (most recent assistant turn + tool results).
Destroying stdout instead lets writes fail fast and the natural function
return drive cleanup. We deliberately do not also abortController.abort()
here: the abort path runs handleCancellationError which itself calls
process.exit(130), re-introducing the same bypass.
Reported by zhangxy-zju on #3581.
* fix(cli): bound runExitCleanup with per-fn + wall-clock timeouts
Pre-fix, runExitCleanup was an unbounded series of awaits. After the
async-jsonl change moved chat-recording writes off the calling thread
(Config.shutdown now `await flush()`s the queue), any hung syscall
(slow disk, dead NFS mount, stuck MCP socket, telemetry HTTP stall)
would hang process exit indefinitely — sync writes were inherently
bounded by syscall return; async writes are not.
Adds per-cleanup 2s + overall 5s wall-clock failsafes on the same
shape as Claude Code's gracefulShutdown.ts. Also replaces dead
test-isolation code (`global['cleanupFunctions']` was never on global,
the array is module-private) with a `_resetCleanupFunctionsForTest`
hook matching the convention from d6485964c.
Follow-up flagged by zhangxy-zju on #3581.
---------
Co-authored-by: wenshao <wenshao@U-K7F6PQY3-2157.local>
* feat(vscode): add native context menu copy actions for webview chat
Add three right-click context menu items to the chat message area using
VSCode's native webview/context API:
- Copy Message: copies the right-clicked message's raw markdown content
- Copy All Messages: copies the full conversation in markdown format
- Copy Last Reply: copies the last assistant response
Implementation details:
- Commands registered in package.json with webview/context menu entries
- Clipboard writes go through extension host (vscode.env.clipboard) for
reliability in webview sandbox
- Message identification via data-msg-idx stamped after render
- Tool-call outputs supported including diff format (git diff style)
- i18n support via package.nls.json (English) and package.nls.zh-cn.json
- Menu only shown in message area (not input box or empty state)
Closes#3052
* fix(vscode): wrap tool-call content text in code blocks for copy
* fix(vscode): only wrap tool-call content in code blocks for Copy All, not single Copy Message
* fix(vscode): route copy commands to the right-clicked webview and use dynamic code fences
* fix(vscode): use childIndexMap for copy-message routing and extract shared message handling
Replace the wrapper-div approach (which broke CSS layout) with a
render-time childIndexMap that maps DOM child positions to allMessages
indices. This avoids both the useLayoutEffect index-drift bug and the
wrapper-div CSS side effects.
- Remove data-msg-idx wrapper divs; messages render directly as
container children, preserving original [&>*] CSS layout
- Build childIndexMap during MessageList render, skipping null items
(empty AssistantMessage, hidden tool calls via shouldShowToolCall)
- findMessageIndex walks up from click target to container's direct
child, then maps through childIndexMap
- Filter hidden tool calls and empty content in copyAllMessages
- Extract handleCommonWebviewMessage to deduplicate routing logic
across sidebar, editor panel, and restored panel handlers
- Clear lastContextMenuProvider on dispose to prevent memory leaks
* fix(vscode): handle image messages in copy and resolve intermittent copy failure
- Copy Message on image messages now outputs markdown format 
instead of empty string
- Copy All Messages includes image messages as  instead of
skipping them
- Copy Last Reply skips empty assistant placeholders during streaming
- Resolve intermittent copy failure by pre-resolving message index on
right-click instead of storing a DOM element reference that can become
stale after React re-renders
* feat(vscode-companion): support /export session command
* fix(vscode-ide-companion/webview): prefer ACP session id for export
* feat(vscode-ide-companion): support /export slash command
Add nested /export completion and ACP command availability for the VS Code companion.
Reuse the shared export flow, write to the default path, and show clickable export results in chat.
* fix(export): align slash command messaging
Restore the CLI export description to the existing wording.
Keep the VS Code companion error message consistent with the required /export subcommands.
* fix(webui): support explicit markdown file links
Handle local markdown file links in assistant messages even when automatic file-link detection is disabled.
Normalize encoded paths and line fragments so exported files can be opened from the VS Code webview.
* test(vscode-ide-companion): make export path assertion cross-platform
* fix(vscode-ide-companion): use public session export entrypoint
* fix(cli): replay standalone ESC after early capture
* fix(vscode-ide-companion): resolve rebase artifacts and vitest export alias
Remove duplicate AvailableCommand import caused by merge, and add
vitest resolve alias for @qwen-code/qwen-code/export so the session
export service tests can resolve the CLI export module from source.
* fix(cli): fix getAvailableCommands test mock to use getCommandsForMode
The test mock was only setting up getCommands but getAvailableCommands
calls getCommandsForMode. Add getCommandsForMode to the mock and set up
test data on it instead.
* fix(vscode-ide-companion): fix export file link click and add save dialog
- Fix file:/// URI handling in MarkdownRenderer: normalizeExplicitFileLink
now strips the file:// scheme before checking isAbsolutePath, so exported
file links are properly recognized and clickable
- Replace direct cwd file write with vscode.window.showSaveDialog() so
users can choose the export destination and filename
- Handle cancelled save dialog gracefully (return null, skip success message)
* fix(webui): scope file link handler to file:// URIs only, fix # in filenames
- normalizeExplicitFileLink now returns early for file:// URIs without
splitting on #, since vscode.Uri.file() encodes # as %23 in the path.
This prevents filenames containing # from being truncated after decode.
- Explicit-link click handler now only fires for file:// URI hrefs,
not arbitrary relative paths. This prevents model-generated markdown
links from bypassing enableFileLinks=false and opening arbitrary files.
- Remove unused KNOWN_FILE_EXTENSIONS constant.
* fix(vscode-ide-companion): update export tests for save dialog, fix stale JSDoc
- Add showSaveDialog mock to sessionExportService.test.ts
- Update existing test to verify save dialog is called with correct args
- Add test for cancelled save dialog returning null
- Fix JSDoc that incorrectly claimed fallback-to-cwd behavior
* fix(core): preserve reasoning_content during session resume and active sessions (GH#3579)
* chore(core): remove dead thinkingThresholdMinutes config after latch removal (GH#3579)
#3450 pinned every assistant/thinking segment in a streamed turn to the
same turn-start timestamp so a later user message could not be sorted
between two segments of the previous turn (#3273). That fix turned out
to conflict with the tool-call timeline: tool calls carry their own
arrival timestamp, which is strictly greater than the turn-start
timestamp, so after #3450 every tool call sorted AFTER both assistant
segments instead of between them — the exact 'tool call jumped to the
end' ordering bug users are now reporting.
The two bugs pull the sort key in opposite directions and cannot both
be satisfied by a single timestamp strategy. Roll #3450 back byte-for-
byte on useMessageHandling.ts so the tool-call ordering regression is
fixed immediately; replace the test file with two focused cases that
pin the conflicting invariants so the next fix (likely a monotonic
sequence key shared across messages and tool calls) has a clear
target:
- tool-call interleave test (passes today): a tool call that arrives
between two assistant segments must sort strictly between them.
- #3273 regression test (it.fails today): all assistant segments of
one turn must sort before a user message sent during the turn.
Flipped to a normal it() once the proper fix lands.
Refs: #3273, #3450
Co-authored-by: Qwen-Coder <noreply@qwenlm.ai>
* fix(cli): dispatch queued slash commands through the slash path
When the agent was responding and the user queued a message, the drain
path joined all queued messages with `\n\n` and submitted them as one
prompt. Any slash command in that blob (e.g. `/model`) no longer started
with `/`, so it was sent to the model as plain text instead of opening
the command's dialog.
The mid-turn tool-result drain had the same problem: it drained the
entire queue into the tool-result payload, so a slash command queued
during tool execution was injected as context for the model rather than
executed as a command.
Queue draining now splits into segments — consecutive plain-text
messages are still batched into one submission, while slash commands
are submitted alone so their `/` prefix survives. The mid-turn drain
only takes leading plain-text messages and leaves slash commands
queued for the normal idle drain. The idle drain is gated on open
dialogs so a queued `/model` does not cause the following queued
prompt to be sent to the model while the picker is still open, and a
re-entry lock plus a nonce close the race between state commits and
the async dialog-open.
* fix(cli): defer queued slash commands until idle
* fix(cli): drop queued messages on cancel instead of auto-submitting
Cancel's contract is now "abort and redirect" in both cancel paths:
restore the most recent queued segment into the buffer for editing and
drop the rest, so forgotten follow-ups cannot auto-submit once the turn
settles. Previously the non-tool path left queued plain-text segments
in place for the idle drain to fire, and the tool-executing path
cleared only the buffer — both surprised users with belated message
dispatches after they had already cancelled.
* refactor(cli): batch plain prompts in idle drain
Idle drain now runs in two phases: drain all plain-text prompts into one
turn (drainQueue), then pop slash commands one-by-one (popNextSegment).
Mirrors the mid-turn behavior so queue handling is consistent across
mid-turn and idle contexts.
popAllMessages now drains the entire queue joined with \n\n for Ctrl+C
cancel and ESC/Up edit-restore. Drop the unused options parameter from
useMessageQueue and the extractFirstSegment helper.
---------
Co-authored-by: 愚远 <zhenxing.tzx@alibaba-inc.com>
* fix(cli): disable Kitty keyboard protocol on SIGINT to prevent garbled 9;5u output
When a Kitty-capable terminal (iTerm2, Kitty, WezTerm) is used, the CLI
enables the Kitty keyboard protocol at startup via ESC[>1u. On exit, the
protocol must be disabled with ESC[<u to restore the terminal's default
key encoding. Failing to do so leaves the terminal in Kitty mode: any
subsequent Ctrl+C press is encoded as ESC[99;5u, and since the shell does
not understand this sequence, it echoes the trailing '9;5u' as garbled
text.
Root cause: kittyProtocolDetector registered cleanup handlers for 'exit'
and 'SIGTERM', but omitted SIGINT. A process terminated via SIGINT (e.g.
kill -INT <pid>, a parent process sending SIGINT, or certain process
managers) would exit without disabling the protocol.
Fix:
1. Add process.on('SIGINT', disableProtocol) alongside the existing
'exit' and 'SIGTERM' handlers in kittyProtocolDetector.ts.
2. Export a new disableKittyProtocol() function for explicit call sites.
3. Call disableKittyProtocol() in the registerCleanup callback in
gemini.tsx before instance.unmount(), so the disable sequence is
written while stdout is fully operational regardless of exit path.
Fixes#3528
* fix(test): add disableKittyProtocol to kittyProtocolDetector mock
* fix(cli): run ACP Agent tool calls concurrently (#2516)
When the model returns multiple Agent tool calls in a single turn, the
ACP Session previously executed them sequentially in a plain for-loop,
multiplying latency by the number of sub-agents spawned.
Mirror the partition logic in coreToolScheduler.partitionToolCalls:
consecutive Agent calls form a parallel batch (safe because sub-agents
have no shared mutable state); any other tool forms its own sequential
batch so the model's implicit ordering is preserved. Response-part
ordering still matches the original functionCalls order.
Add a focused test that uses controllable deferred executes to prove
both Agent calls start before either resolves, and that the fed-back
functionResponse ordering is stable regardless of resolution order.
* Address PR #3463 review: bound concurrency + robust test timing
Two issues raised by the /review bot:
1. The raw Promise.all fan-out bypassed the bounded-concurrency guard
that coreToolScheduler applies via QWEN_CODE_MAX_TOOL_CONCURRENCY.
Replaced with an inline runBounded helper that mirrors core's
runConcurrently (Promise.race on a bounded executing set, default
cap 10), keeping in-order result collection.
2. The concurrency test used a 10-iteration microtask yield loop before
asserting both execute() spies had been invoked. That's fragile —
runTool's pre-execute path (build → getDefaultPermission →
evaluatePermissionRules → permission branch → PreToolUseHook) has
more await boundaries than 10 ticks guarantees, and the CI run
reported call-a still at 0 invocations at the assertion point.
Reworked the test to wait on an explicit `called` deferred that
resolves *inside* the execute() mock body. Under sequential
behaviour only one `called` would ever fire → `Promise.all([called-a,
called-b])` deadlocks → vitest's per-test timeout surfaces the
regression. Under the fix both fire before either result resolves.
* fix(acp): degrade gracefully when AgentTool invocation has no eventEmitter
The concurrency test for #2516 timed out on CI with "Test timed out in
5000ms" after the `await Promise.all([called-a, called-b])` rewrite in
the previous review-fix commit. The 5000ms wait was the symptom; the
root cause is that neither `execute()` was ever being called.
runTool's AgentTool branch was guarded with `'eventEmitter' in invocation`,
which is a *key-presence* check. The test mock provides
`{ eventEmitter: undefined, ... }` — the key exists (value undefined),
the branch is entered, and `SubAgentTracker.setup` immediately throws
inside `eventEmitter.on(...)`. The try/catch in runTool swallows the
throw and returns an error response, so `invocation.execute()` never
runs, `called[id].resolve()` never fires, and the test deadlocks.
The earlier review commit (4519c5f9c) interpreted the CI symptom as
"10 microtask yields aren't enough" and rewrote the assertion around a
deferred `Promise.all`. But the old test's `toHaveBeenCalledTimes(1)`
failure with 0 invocations was already the same bug — execute was never
called. The new formulation just converted the visible failure from an
assertion mismatch into a timeout.
Switch the guard to a truthy check against `invocation.eventEmitter`.
Semantics for real AgentTool are unchanged — `agent.ts:392` declares
`readonly eventEmitter: AgentEventEmitter = new AgentEventEmitter()`,
so production always enters the branch. The only new behavior is that
incomplete invocations (or test mocks) skip SubAgentTracker setup
cleanly instead of crashing. `subAgentCleanupFunctions` stays `[]`,
so the cleanup forEach at the success/error paths is a no-op.
In ACP mode, the Mcp server list sent by the IDE client can include
SSE (type: "sse") and HTTP (type: "http") transports, but the previous
implementation only handled stdio servers via toStdioServer(). Non-stdio
servers were silently skipped (continue), so any SSE/HTTP-configured
MCP server would never be registered.
Changes:
- Add toSseServer() helper: detects type=="sse" servers and maps them
to MCPServerConfig(url=..., headers=...)
- Add toHttpServer() helper: detects type=="http" servers and maps them
to MCPServerConfig(httpUrl=..., headers=...)
- Refactor newSessionConfig() loop to handle all three transport types
- Declare mcpCapabilities: { sse: true, http: true } in agentCapabilities
so IDE clients know this agent supports these transports without needing
a transparent proxy
- Export the three helper functions for unit testing
Tests:
- Unit tests for toStdioServer / toSseServer / toHttpServer helpers
(type discrimination, mutual exclusion)
- Integration-style tests for QwenAgent.initialize() mcpCapabilities
- Integration-style tests for newSession() with SSE/HTTP MCP servers,
verifying MCPServerConfig is constructed with the correct arguments
(url vs httpUrl, headers passthrough, empty-headers → undefined)
Fixes#3472