Files
qwen-code/HOSTED_SHELL_UNICODE_E2E.md
T
Heyang Wangandwangheyang 8666ab091f fix(cli): reject malformed hosted shell Unicode before admission (#13678)
* fix(cli): reject malformed hosted shell Unicode before admission

Prevent lone UTF-16 surrogates from reaching Workspace acquisition
or execution while preserving valid Unicode and durable history.

- Refuse whole batches before Hooks and native tool admission
- Stop malformed sequential Hook rewrites and restore safe refusals
- Suppress post-tool callbacks for live and replayed Unicode refusals
- Cover local/O2 persistence, same-turn correction, and Session reuse
- Document the admission design and local acceptance evidence

* fix(cli): preserve durable hosted shell Unicode refusals

Keep malformed Shell input refusals bounded and recoverable after
Hook rewrites without losing policy denials or the original store error.

- Normalize optional arguments and validate reduced refusal evidence
- Fold nested permission rewrites and bound assembled Hook receipts
- Read recovery resources concurrently while preserving event order
- Cover MCP and background guards, sibling denials and failed writes
- Stabilize Hook admission waits and verify the second Session alone

* fix(cli): keep hosted Unicode refusals replayable and batch-wide

Keep durable Unicode refusals correctable across terminal Hook
recovery and bounded receipts, without admitting sibling tools.

- Accept proven-unstarted Hooks while rejecting blocked evidence
- Preserve a fixed Unicode reason in byte-bounded Hook refusals
- Retain each sibling's own argument validation error
- Pin MCP admission, Hook handoff and continuation regression cases

* fix(cli): recover cancelled size-refused hook plans

Treat cancelled Hook plans as terminal when their durable evidence
proves a result or no dispatch, so Unicode batch refusals can replay.

- Preserve failure-closed checks for unresolved or contradictory evidence
- Cover bounded permission refusals and independent recovery guard failures
- Assert Java capture lanes and single successful dispatch in both sessions

* test(sdk-java): scope Unicode publication checks per session

Shared publication rows made the local gate depend on invocation order.
A global positive count also hid a missing secondary-session capture.

- Filter publication counts by the current tenant and each session
- Preserve local zero and o2 positive checks for both sessions

* test(sdk-java): verify hosted Unicode capture completion

Make local publication checks run against an available service,
and distinguish completed o2 captures from reservation rows.

- Register publication fixtures for both capture modes
- Require referenced and complete results in each o2 session

* test(sdk-java): align Unicode gate with managed schema

Keep the hosted Unicode oracle runnable after schema consolidation.
Use canonical tables without changing the per-session completion
and single-dispatch assertions.

- Query captures from ma_output_capture
- Query dispatch evidence from ma_tool_execution

* test(managed-agent): align Unicode gate with main contracts

Keep the hosted Unicode gate compatible with the server's updated
Checkstyle rules and object-store contract after the module move.

- Add braces around the missing-object fixture refusal
- Add braces around the local-mode empty-capture assertion
- Align the fixture with the current object-store validation method

* test(managed-agent): register native macOS workspace fixtures

The Hosted tool-turn fixture registered guarded workspaces only on
Linux, leaving corrected Shell calls unavailable on supported macOS
hosts after malformed input had been refused.

- Match fixture registration to the native storage guard platforms
- Exercise the existing physical checks for both supported host systems

Native macOS local/O2 tests and the non-Unicode workspace control pass
with the original assertions, physical guards and execution deadlines.

---------

Co-authored-by: wangheyang <[email protected]>
2026-10-23 02:16:46 +00:00

5.2 KiB

Hosted Shell Unicode E2E

This guide covers the real-process gate for the fix described in the English design and the Chinese design. It covers one behavior: a Hosted Shell call that carries an unpaired UTF-16 surrogate is refused before Workspace admission, the refusal is durable, and the model can correct the call in the same turn.

What the test proves

The gate runs one JUnit parameterized test with two capture paths, local and o2. For each capture path it proves these points:

  • Eight standalone malformed batches produce ordered, paired tool errors. The matrix is command and description, each with \ud800, \udc00, 😀\ud800, and \udc00\ud800.
  • The only Broker requests these batches make end in /runtimes:warm, which means no acquire, prepare, or start request. Runtime warming starts before the model output, so the test allows it.
  • The driver observes no new entry in the publication reservation list.
  • Neither the malformed shell command nor its valid write_file sibling creates a file.
  • Session detach and load keep the original arguments and the paired errors.
  • A corrected command runs once in the same prompt turn. It preserves Chinese text, emoji, combining characters, and the literal six-character \ud800.
  • A second Session on the same Workspace runs a valid call. Neither path becomes recoveryBlocked.

The test uses the hosted-workspace-shell/1 profile. The /2 profile has unit coverage only.

Prerequisites

  • Node.js 22 or later.
  • JDK 21. Set JAVA_HOME to it.
  • Maven 3.8.9 or later.
  • Installed workspace dependencies.

From the repository root:

corepack pnpm install --frozen-lockfile

Build the CLI

The Java gate runs the bundled CLI as the Runtime worker, so build it first:

npm run build
npm run bundle

The gate reads dist/cli.js. Set qwen.cli.entry to that file when you run the test.

Install the Java prerequisite

The Runtime Broker module no longer has its own POM; the server module uses the qwencode module:

mvn -q -f packages/sdk-java/qwencode/pom.xml -DskipTests -Dgpg.skip=true install

Run the gate

Compile the server and its tests first:

mvn -f packages/managed-agent-server/pom.xml -DskipTests test-compile

Then run the single test method through the failsafe plugin:

mvn -f packages/managed-agent-server/pom.xml \
  -Phosted-workspace-tools \
  -Dnode.executable="$(command -v node)" \
  -Dqwen.cli.entry="$PWD/dist/cli.js" \
  -Dit.test='HostedWorkspaceToolTurnIT#malformedShellUnicodeIsCorrectableBeforeAcquisition' \
  failsafe:integration-test failsafe:verify

Do not add -Dtest=none or -Dsurefire.failIfNoSpecifiedTests=false. The module sets failIfNoTests to true, and that combination stops the build at surefire with No tests were executed!.

Success conditions

The run passes when all of these lines appear:

HOSTED_SHELL_UNICODE_OK: local, 9 refusals per capture, durable reload, Unicode correction, second Session
HOSTED_SHELL_UNICODE_OK: o2, 9 refusals per capture, durable reload, Unicode correction, second Session
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS

The Druid testWhileIdle is true, validationQuery not set line is expected and does not indicate a failure.

Recorded result

Measured on macOS arm64 with Node.js 22.23.3, JDK 21.0.13, and Maven 3.9.9, on the merge of main ce8a94706b into the PR branch:

HOSTED_SHELL_UNICODE_OK: local, 9 refusals per capture, durable reload, Unicode correction, second Session
HOSTED_SHELL_UNICODE_OK: o2, 9 refusals per capture, durable reload, Unicode correction, second Session
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 11.79 s -- in com.alibaba.qwen.code.managedagent.HostedWorkspaceToolTurnIT
[INFO] BUILD SUCCESS

Test environment

The gate runs in one JVM and starts these real components:

  • The managed-agent server with a temporary H2 database in MySQL mode.
  • An EmbeddedRuntimeBroker.
  • A real Node worker process that runs dist/cli.js.
  • A deterministic fake OpenAI server that sends the malformed JSON escapes.

O2 uses the real Java publication controller and SQL grant and data services. The object store is an in-memory test adapter. No model API key and no OSS account are needed.

This is not a MySQL, OSS, Linux, or Windows acceptance run. It also does not cover a real hosted-workspace-shell/2 process.

Troubleshooting

  • POM ... does not exist: use packages/sdk-java/qwencode/pom.xml. The runtime-broker POM was consolidated and no longer exists.
  • No tests were executed!: you passed -Dtest=none. The module sets failIfNoTests to true, so surefire stops the build. Use the command above.
  • Pass -Dnode.executable with an absolute Node.js 22+ path: add -Dnode.executable="$(command -v node)".
  • AssertionError about dist/cli.js not being a regular file: run npm run bundle and check qwen.cli.entry.
  • recoveryBlocked in the failure dump: the refusal was not recognized on reload. Check that the assistant message and both tool errors were persisted, and that the batch has no native tool intent.