* 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]>
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
commandanddescription, each with\ud800,\udc00,😀\ud800, and\udc00\ud800. - The only Broker requests these batches make end in
/runtimes:warm, which means noacquire,prepare, orstartrequest. 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_filesibling 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_HOMEto 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: usepackages/sdk-java/qwencode/pom.xml. Theruntime-brokerPOM was consolidated and no longer exists.No tests were executed!: you passed-Dtest=none. The module setsfailIfNoTeststotrue, 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)".AssertionErroraboutdist/cli.jsnot being a regular file: runnpm run bundleand checkqwen.cli.entry.recoveryBlockedin 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.