qwen-code/docs/users/features
ChiGao 9e26424aa7
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
feat(cli): add dual-output sidecar mode for TUI (#3352)
* feat(cli): add dual-output sidecar mode for TUI

Adds an optional **dual-output** mode for the interactive TUI: while Qwen
Code keeps rendering normally on stdout, it concurrently emits a structured
JSON event stream on a second channel (--json-fd / --json-file) and
optionally watches a JSONL command file (--input-file) for prompts and
tool-permission responses written by an external program.

This unlocks programmatic embedding of the TUI from IDE extensions, web
frontends, CI agents, or automation scripts without forcing them to give
up the rich interactive UI in favor of --output-format=stream-json.

## Design

The TUI already has a battle-tested JSON event emitter
(`StreamJsonOutputAdapter`). This change makes that adapter pluggable on
its output stream and wires a small `DualOutputBridge` that forwards TUI
events to a second instance of the adapter writing to fd / file.

For tool approvals, when a tool enters awaiting_approval the bridge emits
`control_request` (subtype `can_use_tool`); whichever side resolves first
(TUI's native UI or `confirmation_response` via --input-file) wins, and a
`control_response` is mirrored back so all observers stay in sync.

`session_start` is announced once when the bridge is constructed so
consumers can correlate the channel with a session before any other event
arrives.

## CLI surface

- `--json-fd <n>` — write JSON events to fd n (n >= 3; provided via spawn
  stdio).
- `--json-file <path>` — write JSON events to a file / FIFO / /dev/fd/N.
- `--input-file <path>` — watch this file for JSONL commands.

`--json-fd` and `--json-file` are mutually exclusive. fds 0/1/2 are
rejected to prevent corrupting the TUI.

## Wire protocol

Output: existing stream-json schema with `includePartialMessages` always
enabled, plus:

- `system` / `subtype: session_start` — emitted once on bridge
  construction.
- `control_request` / `subtype: can_use_tool` — pending tool approval.
- `control_response` — final approval outcome (mirrors TUI-native or
  external resolution).

Input (--input-file):

    {"type":"submit","text":"What does this function do?"}
    {"type":"confirmation_response","request_id":"...","allowed":true}

`submit` is queued and retried when the TUI returns to idle.
`confirmation_response` is dispatched immediately — a pending tool call
is blocking and the response cannot wait behind earlier submits.

See `docs/users/features/dual-output.md` for the full schema, latency
notes, failure modes, and a spawn example.

## What changes when the flags are absent

Nothing. The bridge and watcher are constructed only when the relevant
flags are set; otherwise the React Context providers carry `null` and
every callsite short-circuits. No overhead, no behavioral change for
existing users.

## Failure handling

- Bad fd / unopenable path → warning on stderr, dual output stays
  disabled, TUI launches normally.
- Consumer disconnect (EPIPE) → bridge silently disables itself, TUI
  keeps running.
- Any exception inside the adapter → caught, logged, bridge disabled.
  The TUI is never crashed by a dual-output failure.

## Files

New:
- packages/cli/src/dualOutput/{DualOutputBridge,DualOutputContext,index}.{ts,tsx}
- packages/cli/src/remoteInput/{RemoteInputWatcher,RemoteInputContext,index}.{ts,tsx}
- packages/cli/src/nonInteractive/io/index.ts
- docs/users/features/dual-output.md

Modified:
- packages/core/src/config/config.ts — 3 new ConfigParameters fields + getters
- packages/cli/src/config/config.ts — yargs options + mutex validation
- packages/cli/src/gemini.tsx — instantiate bridge / watcher in
  startInteractiveUI, wrap with Context Providers, register cleanup
- packages/cli/src/ui/AppContainer.tsx — connect RemoteInput to
  submitQuery, bridge tool confirmations
- packages/cli/src/ui/hooks/useGeminiStream.ts — call
  dualOutput?.processEvent(...) at five existing event points
- packages/cli/src/nonInteractive/io/{Base,Stream}JsonOutputAdapter.ts —
  StreamJsonOutputAdapter accepts an injected output stream; base adapter
  exposes emitPermissionRequest / emitControlResponse through a new
  emitControlMessageImpl hook (default no-op in batch mode).

## Tests

- packages/cli/src/dualOutput/DualOutputBridge.test.ts — fd validation,
  auto session_start, control-event routing, post-shutdown safety.
- packages/cli/src/remoteInput/RemoteInputWatcher.test.ts — submit
  forwarding, immediate confirmation dispatch, busy/idle retry,
  malformed-line tolerance, shutdown.
- packages/cli/src/nonInteractive/io/StreamJsonOutputAdapter.dualOutput.test.ts —
  custom outputStream injection and new emitPermissionRequest /
  emitControlResponse paths.

tsc --noEmit -p packages/cli/tsconfig.json is clean.
vitest run src/nonInteractive src/dualOutput src/remoteInput → 297 passed,
1 skipped, 11 files.

* feat(cli): dual-output capability handshake, session_end, control_error, settings.json

Incremental improvements on top of the initial dual-output PR based on
reviewer feedback. All extensions are additive; older consumers that
ignore unknown fields keep working.

## Capability handshake in session_start

`session_start.data` now carries three new fields so consumers can
feature-detect without sniffing the stream:

- `protocol_version` (integer, currently 1) — bumped on any protocol
  change consumers might care about.
- `version` (string) — the Qwen Code CLI version, threaded in from
  `gemini.tsx`.
- `supported_events` (string[]) — the event kinds this bridge version
  is known to emit, exported as `SUPPORTED_EVENTS` from the module.

## session_end on bridge shutdown

DualOutputBridge.shutdown() now emits a final
`system` / `session_end` event carrying `session_id` before closing the
stream. Gives consumers a definitive termination signal rather than
requiring them to infer it from EPIPE. Idempotent — calling shutdown
twice emits exactly one session_end.

## control_error emission path

`ControlErrorResponse` (already defined in types.ts) now has a first-
class emission path: `BaseJsonOutputAdapter.emitControlError(requestId,
message)` → `control_response` with `subtype: 'error'`. Wired into
AppContainer's remote-input confirmation handler so that a
`confirmation_response` referencing an unknown / already-resolved
request_id produces a structured error reply instead of silently
dropping, letting consumers retry or surface the error.

## settings.json support

New `dualOutput` top-level settings block with `jsonFile` and
`inputFile` properties. `--json-fd` has no settings equivalent (fd
passing is a spawn-time concern). CLI flag wins over settings when
both are present, so scripted one-off runs still work unchanged.
`requiresRestart: true` since the bridge is constructed once at
startup.

## Documentation

`docs/users/features/dual-output.md` gains three major sections:

- **Use cases** — concrete integration scenarios (terminal+chat dual
  sync, IDE extensions, web frontends, CI observers, multi-agent
  orchestration, session replay, observability, QA).
- **Why two output flags?** — detailed rationale for coexisting
  `--json-fd` and `--json-file`, including the PTY constraint
  (`node-pty` / `bun-pty` expose no stdio array, and `forkpty(3)` /
  `login_tty` actively close fds >= 3 before exec).
- **Comparison with Claude Code's stream-json** — schema-parity
  matrix, transport-topology differences, permission-control-plane
  behavioral notes, and a "room to improve" section as a design
  horizon.
- **Runnable demos** — seven copy-paste POCs: event observer, remote
  submit, permission bridge, Node embedder with capability
  feature-detection, session_end handling, failure drills.
- **Settings-based configuration** — example settings.json snippet and
  precedence rules.

## Tests

- DualOutputBridge.test.ts: new cases for capability handshake shape,
  session_end on shutdown, shutdown idempotency, and emitControlError.
- StreamJsonOutputAdapter.dualOutput.test.ts: new case for
  emitControlError at the adapter level.

302 passed, 1 skipped, 11 files. tsc --noEmit -p packages/cli is clean.

* docs(dual-output): shrink Claude Code comparison to one honest sentence

After actually reading the Claude Code source (src/cli/structuredIO.ts,
src/bridge/*, src/utils/messages/systemInit.ts), the previous
"Comparison with Claude Code's stream-json" section was overstated:

- Claude Code has no equivalent of TUI + sidecar running simultaneously.
  Its stream-json only works with --print (non-interactive); the bridge
  in src/bridge/* is Anthropic's own remote worker protocol, not a
  local embedding surface.
- CC uses `system/init` (not `session_start`) and has no session_end in
  the wire protocol, so the schema-parity table contained false ticks.
- Framing this PR as "parity with Claude Code" is therefore inaccurate;
  it's filling a gap Claude Code does not address.

Replace the whole multi-section comparison (schema matrix, transport
table, permission notes, borrow list, roadmap) with a single sentence
stating the accurate relation: same event format in spirit, different
topology — CC's is non-interactive only.

* fix(cli): address review feedback on dual-output sidecar mode

- Fix control_response mirror: external-initiated confirmations now
  emit control_response via the same mirror useEffect as TUI-native
  resolutions, making the emission path symmetric for all observers.
- Fix ENOENT: --json-file with a non-existent path now falls back to
  createWriteStream (auto-creates the file) instead of throwing.
- Fix race: add reading guard to RemoteInputWatcher.readNewLines()
  preventing duplicate command processing on rapid appends.
- Refactor confirmationHandler to use refs (pendingToolCallsRef,
  dualOutputRef) and register once (deps: [remoteInput]) to eliminate
  teardown/re-registration churn.
- Add debug logging to shutdown bare catch for ops correlation.
- Add ENOENT fallback test case for DualOutputBridge.
- Regenerate settings.schema.json for dualOutput section.

Generated with AI

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

* fix(cli): make RemoteInputWatcher poll interval configurable for CI reliability

RemoteInputWatcher.test.ts was timing out in CI (5s default) because
fs.watchFile's 500ms poll interval is unreliable under load. Fix:

- Accept optional `pollIntervalMs` in constructor (default 500ms).
- Tests use 100ms poll interval for faster feedback.
- Increase per-test timeout to 15s and waitFor timeout to 10s.
- Increase "TUI busy" wait from 800ms to 1500ms for CI headroom.

Generated with AI

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

* fix(cli): eliminate fs.watchFile timing dependency in RemoteInputWatcher tests

Tests were flaky across all CI platforms (macOS/ubuntu/windows) because
fs.watchFile polling (even at 100ms) is unreliable under CI load.

Fix: expose checkForNewInput() as a public method that directly triggers
file reading and returns a Promise. Tests now call it synchronously after
writing to the input file — no polling, no timeouts, deterministic.

Also fixes:
- Windows ENOTEMPTY: add delay in afterEach before rmSync
- Add active check in readNewLines to respect shutdown state
- readNewLines now returns Promise<void> for awaitable reads

Generated with AI

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

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-18 02:14:53 +08:00
..
channels feat(channels): add dispatch modes and prompt lifecycle hooks 2026-03-28 06:19:02 +00:00
_meta.ts feat(cli): add dual-output sidecar mode for TUI (#3352) 2026-04-18 02:14:53 +08:00
approval-mode.md fix: rename /plan execute to /plan exit 2026-04-08 10:21:00 +08:00
arena.md docs(arena): add Agent Arena documentation 2026-03-18 16:26:59 +08:00
checkpointing.md feat: restructure docs 2025-12-04 18:26:05 +08:00
code-review.md docs: add zero-findings PR tip to follow-up table 2026-04-07 15:33:03 +08:00
commands.md docs: update authentication methods to reflect OAuth discontinuation (#3325) 2026-04-17 15:34:18 +08:00
dual-output.md feat(cli): add dual-output sidecar mode for TUI (#3352) 2026-04-18 02:14:53 +08:00
followup-suggestions.md fix: improve /model --fast description clarity and prevent accidental activation (#3077) 2026-04-10 12:09:46 +08:00
headless.md feat: add system prompt customization options for CLI and SDK 2026-03-16 03:06:35 +08:00
hooks.md fix(cli): remember "Start new chat session" until summary changes (#3308) 2026-04-16 13:54:14 +08:00
language.md Add Japanese 2026-01-02 15:53:20 +09:00
lsp.md fix(lsp): improve C++/Java/Python language server support 2026-03-21 01:27:00 +08:00
mcp.md refactor: remove deprecated list and refresh MCP commands 2026-02-25 11:58:41 +08:00
memory.md feat(cli): support multi-line status line output (#3311) 2026-04-17 12:44:30 +08:00
sandbox.md feat(cli): support tools.sandboxImage in settings (#3146) 2026-04-13 09:43:34 +08:00
scheduled-tasks.md docs(scheduled-tasks): add documentation for /loop and cron scheduling features 2026-03-29 12:56:28 +08:00
skills.md feat: promote Agent Skills from experimental to stable 2026-02-06 13:04:08 +08:00
status-line.md feat(cli): support multi-line status line output (#3311) 2026-04-17 12:44:30 +08:00
sub-agents.md feat(core): implement fork subagent for context sharing (#2936) 2026-04-14 14:27:38 +08:00
tips.md feat: add contextual tips system with post-response context awareness (#2904) 2026-04-13 17:40:27 +08:00
token-caching.md feat: update docs 2025-12-12 19:37:36 +08:00