Repository navigation
Conversation
…M#12681) * feat(sdk-java): Add the W0a Managed Workspace binding contract Add the first W0 slice of the Managed Agent proposal (QwenLM#12380): the Workspace binding contract, as a framework-neutral Java module with a TypeScript twin of its directory rule and digest. Nothing is wired yet. - Workspace Registry record, a tenant-scoped read contract, and an immutable snapshot built from deployment configuration, with a successor check against dropped Workspaces, lower generations and storage swaps without a new generation. - Actor access (NONE, READ, CREATE) and an explicit-grant policy. - WorkspaceCatalog lists the Workspaces an actor can read, with a default that does not depend on the page, and resolves a Session's selection. Missing, foreign and unreadable Workspaces fail alike, and nothing falls back to a default or a launch directory. - The lexical rule and normal form of cwdRelative. The drive-prefix check runs on the normal form, so ./C:x cannot turn into C:x. - ContextBinding and its contextDigest: SHA-256 over length-prefixed UTF-8 items with a domain tag, with integers as decimal text. - Shared fixtures, 70 path cases and 39 binding cases computed by an independent implementation, run by both languages. - Run the new module in the SDK Java workflow's Java 21 jobs. * refactor(sdk-java): Move the W0a Workspace binding into runtime-broker QwenLM#12380 settled the placement: the binding is a separate package inside the existing runtime-broker module, not a new Maven module. Move the code to com.alibaba.qwen.code.runtimebroker.managedworkspace and drop the module's pom, CI steps, workflow-guard tests and size-baseline change; the SDK Java workflow already runs the runtime-broker tests and Checkstyle. The package name keeps clear of the repository's unanchored `workspace/` ignore rule, which would silently drop new files. The runtime-broker README and QWEN.md gain a section on the package. Tighten the contract where review found gaps: - resolve raises workspace_not_found for missing, foreign, impossible and unreadable Workspaces from one site, and consults the access policy before the state of a default Workspace; - the TypeScript encoder reads each binding field once and rejects a binding that is not an object; - a snapshot rejects a defaults map holding two equal tenant keys. Pin the rules in both languages with character probes over the whole Basic Multilingual Plane and the astral blocks that mimic ASCII, and with fixtures for lone-surrogate orders, dot segments with spaces and violations deep in long paths. Java tests now also cover content and case-sensitive comparisons, constructor visibility, significant spaces, locale-independent test data and the listing's scan bounds. The design document records the decided placement and moves the boot envelope to its own follow-up change.
…ntials (QwenLM#12680) * perf(cli): keep running in place when env files only hold model credentials The env-file relaunch rule from QwenLM#12602 fires for any value that `.env` files or `settings.env` inject, including the API key that `/auth` stores and the keys the docs recommend for `~/.qwen/.env`. Those users always paid the second boot. Providers read credentials and endpoints per request, never while a module loads, so `hasLoadedEnvironmentValues()` now ignores the documented per-auth-type variables and the `envKey` of every configured model provider. Any other injected value still forces a fresh image, and a provider entry can never exempt a loader or boot-time key. The DashScope proxy base URL was the one such variable read at module load; it is now read at call time. Reported by @wenshao in the QwenLM#12622 review (F2). Claude-Session: https://claude.ai/code/session_01163b7X4Ntw1S2pr9VrtRrs * feat(scripts): let the startup benchmark load credentials from a file `--credentials settings|dotenv` moves the stub's key and URL from the shell into the settings file's `env` block or `~/.qwen/.env`, so the env-file relaunch rule can be measured. The default stays `shell`. Claude-Session: https://claude.ai/code/session_01163b7X4Ntw1S2pr9VrtRrs
…QwenLM#12671) * feat(cli): Declare the v2 execute/status/cancel Managed Runtime contract Extend the owned managed-runtime route manifest with the three tool operations and add their shared schema and conformance fixtures. The wire shape fixes the evidence rules the recovery design demands: every call is keyed by the original reference (sessionId, promptId, callId, argsDigest) rather than any Broker-side identifier; status is read-only and answers unknown with 200 instead of 404 or 500, because a missing record is never evidence that a call did not run; a settled state is the only one that may carry a result; and status additionally returns the Runtime's own lastSequence cursor. Execute accepts up to 256 KiB of request input, and every operation answers at most 1 MiB. No handler is mounted yet: per the manifest rule the routes join in the same line of slices as the execute extraction that implements them. The TypeScript gate test proves the owned-route gate admits exactly these paths, and the Java consumer pins the route contract, every outcome classification, and the closed field sets, so the worker and transport slices that follow share one reviewed target. Related to QwenLM#12380. * fix(runtime-broker): Accept unknown cancellation status Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * feat(cli): Mount the v2 tool operations on the Managed Runtime worker The attestation worker now serves execute, status, and cancel beside attest. Its executor admits exactly the first-slice ordinary tools — read_file, write_file, edit, and foreground run_shell_command — over a real Config rooted at the attested workspace cwd, with checkpointing disabled and no further approval gate, because admission happened on the Harness side. An in-memory journal answers status and cancel by the original reference; it is process-local by construction, since the worker process is the Runtime generation and a restart is a new generation rather than a continuation. execute is idempotent by reference.callId: the same identity joins the in-flight invocation or returns its settled result, and the same callId with a different digest or payload is a 409 identity conflict. status is read-only and answers unknown with 200 for a reference the Runtime never saw. cancel settles a prepared invocation without touching the tool, aborts an executing one, and a cancel the Runtime honored settles as cancelled whether the tool surfaces the abort as an error or as an early result. The worker's HTTP requestTimeout is disabled because an execute call holds its connection until settlement. Related to QwenLM#12380. --------- Co-authored-by: jinye.djy <jinye.djy@U-Y37CF7G3-1943.local> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Shaojin Wen <szujobs@gmail.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2c68513d3d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| public CompletionStage<Void> acquire(RuntimeLease lease, | ||
| RuntimeSession session) { | ||
| return unsupportedSessionVerb(); |
There was a problem hiding this comment.
Implement session verbs before exposing the broker route
When the embedded Runtime Broker is enabled, a fresh /tool-sessions:acquire request reaches RuntimeBrokerService.acquire, which invokes this transport's acquire; the implementation always returns a 501 failed future. EmbeddedRuntimeBroker supplies this same transport for both local-process and static provisioners, so the Hosted Harness cannot acquire any Runtime Session and therefore cannot reach the new control or tool-execution flow. The broker-facing transport needs working session semantics rather than unconditional rejection.
AGENTS.md reference: AGENTS.md:L266-L269
Useful? React with 👍 / 👎.
| try { | ||
| return load(tenantId, sessionId, false); | ||
| } catch (DaemonHttpException error) { | ||
| if (error.getStatusCode() != 404) { | ||
| throw error; | ||
| } | ||
| return create(tenantId, sessionId); |
There was a problem hiding this comment.
Handle an uninitialized durable session before loading
When the Managed Session Store is enabled for a brand-new Java session, this probes loadSession before any create request has initialized the durable journal. The current Hosted Harness can report that state as a non-404 ownership-verification failure, but this catch only falls back to create on 404, so the first accepted turn never creates its Harness Session and repeatedly fails dispatch. Treat the store's not-yet-created state as a create path, or use a create-first/idempotent attach flow.
AGENTS.md reference: AGENTS.md:L270-L274
Useful? React with 👍 / 👎.
| HarnessSessionResolver resolver = sessionId -> { | ||
| SessionRecord session = store.findSessionById(sessionId) | ||
| .orElse(null); |
There was a problem hiding this comment.
Reject non-active sessions in the broker resolver
After Spring restarts, the process-local retired set is empty, while archived and deleted rows remain discoverable here because lookup is by ID alone. An authenticated Harness can therefore warm or acquire a Runtime scope for a persisted session whose lifecycle state is ARCHIVED or DELETED, bypassing the drain decision recorded before the restart. Check the stored session status and fail closed unless it is active.
AGENTS.md reference: AGENTS.md:L270-L274
Useful? React with 👍 / 👎.
…wenLM#12622) * perf(cli): halve fresh-startup time to typeable and cut RSS by 60% Measured with a clean-environment benchmark following the approach in https://claude.dev/blog/how-we-made-claude-ai-faster/ (interleaved A/B, p75 wall-clock backed by deterministic proxies). Against main, time to a typeable prompt goes from 2.55 s to 1.18 s, `qwen -p` time to first request from 2.15 s to 0.94 s, and RSS at the prompt from 574 MB to 231 MB, with no screen change after the prompt becomes usable. - Skip the supervisor relaunch when it adds no Node flags; restarts re-exec in place and updates run in-process (POSIX, plain TUI and -p). - The bin launcher runs the CLI in-process on POSIX. - Enable Node's compile cache on the default route. - Move startup side effects off the critical path: editor probing, the IDE process walk outside VS Code, and the npm-based update check. - Resolve the git branch before the first frame so the footer does not reflow the screen after the prompt is usable. - Bundle with minifyWhitespace; load the review command and non-common highlight grammars only when needed. Experiments, tradeoffs and open decisions are in docs/design/2026-09-24-fresh-startup-performance.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N1XQv6wm9YJ5rSM53eMSRT * fix(cli): keep global.gc in relaunched sessions On POSIX the bin launcher now runs the CLI in-process and exposes gc at runtime, so --expose-gc is no longer in process.execArgv. The supervised relaunch built its child's flags from execArgv alone, so the session process lost global.gc in --acp, stream-json, --json-fd, file input, advanced.autoConfigureMemory, and on Node without process.execve. Both relaunch paths now take their flags from one helper that adds --expose-gc when gc is exposed but not on the command line. * fix(cli): relaunch when env files injected values QwenLM#12602 re-boots a one-shot run whenever .env or settings.env injected values, because modules that read the environment at load time (and Node itself, e.g. NODE_EXTRA_CA_CERTS) only see them in a fresh image. The in-process path ran ahead of that check and skipped it for both interactive and -p. Take the in-process path only when no env-file value was injected; otherwise relaunch as main does. * fix(cli): keep the spawned CLI under Bun The Bun standalone flavor (the OpenTUI preview archives) runs the same bin entry. Bun has no v8.setFlagsFromString, so the in-process path crashed every non-fast-path command at startup on macOS and Linux. Under Bun the entry spawns the CLI with --expose-gc, as on Windows. Also correct the entry's header comment, the relaunch-flags helper's doc comment and a displaced test comment, and narrow the design doc's gc claim to the modes that were checked. * fix(cli): install a deferred update after a clean exit Without a supervising parent, the startup check fell back to "Run /update to install the update." for standalone and update-command installs. Record the request in-process instead: once the session ends cleanly, install the update and exit with its code, so the launcher's exit hook reopens the new version as the parent did on main. A second quit during the install waits instead of cutting it short. * docs(design): record the fresh-startup decisions Resolve the open decisions, and describe the env-file relaunch rule, the Bun launcher path, the restored update-on-exit and the independent reproductions, in both language versions. * docs(design): record decision 7 as a manual harness without a CI gate The follow-up (QwenLM#12674) checks in the benchmark harness for manual runs. The interactive startup counts stop at the typeable prompt, and timer-driven work before it shifts with runner speed, so a CI gate would be flaky. --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 352e2ee93e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| () => managedSelectionFromUrl().sessionId, | ||
| ); | ||
| useEffect(() => { | ||
| saveManagedSelection(activePanel === 'managed', managedSessionId); |
There was a problem hiding this comment.
Preserve host URL parameters when Managed mode is disabled
When managedAgentProvider is absent, activePanel cannot be managed, so this effect immediately calls saveManagedSelection(false, ...) and deletes any existing managed and managedSession query parameters. Consequently, merely mounting the ordinary or embedded WebShell mutates host-owned navigation even though the Managed feature was not enabled; hosts that use either parameter for their own routing lose that state. Only synchronize these parameters when the Managed provider or Managed navigation contract is active.
Useful? React with 👍 / 👎.
| if (sessionStore.isEnabled() && runtimeWorkspaceId != null | ||
| && !runtimeWorkspaceId.isBlank() | ||
| && !workspaceId.equals(runtimeWorkspaceId)) { |
There was a problem hiding this comment.
Validate the store ID against the derived runtime ID
When the Session Store and a local-process Runtime Broker are both enabled but the broker's workspaceId is blank, this check is skipped even though EmbeddedRuntimeBroker.resolveWorkspaceId subsequently derives a concrete ID from workspaceCwd. A separately configured Store ID can therefore differ from the Runtime scope while startup succeeds, causing the Harness journal and Runtime binding to use different (tenantId, workspaceId, sessionId) identities. Compare against the broker's effective derived ID, not only its raw optional property.
AGENTS.md reference: AGENTS.md:L270-L274
Useful? React with 👍 / 👎.
…P mode (QwenLM#9531) * fix(cli): make the Agent Team teammate tab transcript scrollable in VP mode In Virtualized History mode (ui.useTerminalBuffer, on by default) the app owns the whole screen, so there is no terminal-native scrollback. The main conversation view renders its transcript in a ScrollableList virtual viewport, but AgentChatContent rendered the teammate tab transcript through ink's <Static> unconditionally — content that scrolled off screen could not be reached by Page Up, Page Down, arrow keys, or the mouse wheel (QwenLM#9507). Route the teammate transcript through the same ScrollableList viewport in VP mode: header + committed + live items + spinner live in one viewport pinned to the tail, with Page Up/Page Down, Shift+arrows, Ctrl+Home/End and wheel/scrollbar support. The committed/live split is preserved (items from an executing/confirming tool group onward stay on the interactive render path so approval dialogs keep receiving input). The legacy <Static> path for useTerminalBuffer: false is unchanged — native terminal scrollback already works there. * fix(cli): preserve teammate transcript selection * fix(cli): re-measure the agent footer when its height changes AppContainer's controls-height measure effect tracked only main-conversation state, so on the agent tab the Completed/Failed status row, queued messages and input growth never triggered a re-measure: controlsHeight stayed stale-high and the transcript viewport pushed the composer and tab bar past the terminal bottom. AgentComposer now derives a layout key from its height-shifting state (streaming state, status row, queued messages, input text) and syncs it to AgentViewContext; the measure effect depends on that key, mirroring liveAgentPanelLayoutKey from QwenLM#5798. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): use renamed RespondingSpinner in agent transcript viewport Main renamed GeminiRespondingSpinner to RespondingSpinner; the merge kept the old name at the viewport's spinner item and broke typecheck. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): follow RespondingSpinner rename after merging main Main renamed GeminiRespondingSpinner to RespondingSpinner; update the teammate-tab spinner usage and its test mock so the merged tree compiles. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): guard the AgentViewContext layout-key propagation deps (QwenLM#9507) R3-1: the provider link of the QwenLM#9507 layout-key chain was the only link without a source guard. Pin agentComposerLayoutKey in the state memo deps and setAgentComposerLayoutKey in the actions memo deps of AgentViewContext.tsx by extending app-container-controls-dep.test.ts in the PR's established guard style. Mutation-verified: dropping either entry from its deps array (while the object literal keeps it) turns exactly the matching new test red, and every pre-existing test stays green. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): restore full-detail toggle and mouse actions on teammate VP path The virtualized render path added for the teammate tab dropped two things the main conversation view has: - `fullDetail` was not forwarded to either HistoryItemDisplay site, so the app-wide Ctrl+O/Alt+T toggle did nothing for tool groups on this tab while truncated rows kept advertising "... +N chars (ctrl+o)". - ScrollableList arms SGR mouse capture (bypassVpGate), but ContentMouseController was never mounted. With tracking armed the terminal stops handling the mouse itself, so OSC 8 link clicks and the right-click context menu silently did nothing. Forward `fullDetail` on both VP branches (pending and committed) and mount ContentMouseController next to TextSelectionController, mirroring MainContent's VP path. The menu itself is already painted by the shared ContextMenuOverlay in DefaultAppLayout, which sits above this tab. Tests pin both: HistoryItemDisplay receives the toggled `fullDetail` on both branches, and ContentMouseController mounts in VP mode only. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmto4amd1ku * fix(cli): quiet the teammate VP viewport while the context menu is open Mounting ContentMouseController made the right-click menu openable on the teammate tab, but this branch never consulted useContextMenu(), so scroll keys and wheel ticks kept reaching the viewport underneath the pinned menu and a press or drag on a menu item anchored or cleared a transcript selection under the overlay. Mirror MainContent's VP path: gate ScrollableList's hasFocus on contextMenuOpen === null, and pass TextSelectionController eventsPaused={contextMenuOpen !== null} — paused rather than deactivated, since deactivating clears the selection the menu's Copy Selection offers. ContentMouseController stays ungated: it owns the open menu's pointer interaction and its deactivate effect calls closeMenu(). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtq0vr74ni * fix(cli): clear the painted text selection when its controller unmounts The highlight range is painted onto the per-stdout frame controller, which lives outside React, so it outlived the controller that painted it: ink re-reads it on every serialization pass and stamps the background onto whatever cells now occupy those coordinates. A selection made in a teammate transcript therefore survived the tab switch and was re-stamped over rows of the replacing view the user never selected, and no successor could repair it -- clearSelection() early-returns while its own selection is empty, which is what every freshly mounted controller starts with, and the frame-invalidation callback early-returns on the same condition. The teardown is unconditional (it goes straight to setSelection(null) rather than through clearSelection()) and unmount-only: it must never fire on eventsPaused, which exists to keep the selection that the open context menu's "Copy Selection" item offers. getBuffer is a useCallback keyed on [stdout], so the effect is stable and its cleanup runs exactly once, on unmount. This closes the whole class rather than one route: teammate -> main, main -> teammate, and agent -> agent (AgentChatView keys its boundary on agentId) all unmount the surface that owns the selection. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtqfvvmto2 * fix(cli): gate the teammate tab's key consumers on the open context menu (QwenLM#9531) Mounting ContentMouseController made the right-click menu reachable on the Agent Team teammate tab, but the tab's other key consumers did not gate on it: KeypressContext broadcasts to every subscriber and discards return values, so a key aimed at the menu was also delivered to the composer and the approval dialog. The composer now goes quiet on up/down/return/escape while the menu is up (and closes it on any other key, then falls through), and the approval dialog folds the open menu into the same `isFocused` flag it already gates its keypress handler and RadioButtonSelect on. Both mirror the main view's InputPrompt gate. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtyw8o2w11 * fix(cli): keep teammate approvals answerable and gate the last key consumers (QwenLM#9531) R13-1: on the VP path a teammate's pending ToolConfirmationMessage is a tail item inside the windowed list, and dialogsVisible is derived from main-app dialog state only, so one Page Up unmounted the dialog together with its key handler and left the round blocked with nothing on screen. Pull the tail back into view when an approval arrives, then take the scroll keys away - the gate alone would trap a user who had already scrolled up, with no way back. R13-2: KeypressContext broadcasts to every subscriber and discards return values, so an open right-click menu cannot consume a key for anyone. Gate the three remaining consumers on the teammate tab (tab bar arrows, the Shift+Tab approval-mode cycler, the Ctrl+F embedded-shell toggle), matching the two already gated in b479224. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtzfj4451s * fix(cli): keep the deps guard's identifier out of comment prose The source guard in app-container-controls-dep.test.ts substring-matches the raw deps-array text, comments included. Naming liveAgentPanelLayoutKey in this comment meant deleting the real dep entry left the guard green. Reword so the identifier appears only as the dep entry itself; verified by mutation (deleting the dep entry now fails the guard). * fix(cli): drop a queued scroll flush on focus loss and gate the nested answer input Two review Criticals on the teammate tab's approval surface: - ScrollableList left a wheel/drag flush armed across a focus loss, so the timer fired after whatever took focus had pulled the tail into view and scrolled a pending approval dialog back out of the render window, where its keys and this list's were already dead. - AskUserQuestionDialog hard-wired its nested custom-answer TextInput to isActive={true}, so an Enter aimed at an open right-click menu still submitted the half-typed draft as the answer to the pending question. * fix(cli): block stale key handlers while context menus open Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): preserve keys after input dismisses context menu Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…wenLM#12673) * fix(cli): restore ripgrep exec bit on managed npm update activation npm/pnpm pack preserves the exec bit only for `bin` entries, so the published tarball ships every vendor/ripgrep/*/rg as 0644, and the published manifest intentionally carries no postinstall to restore it (QwenLM#6164 removed that surface). The managed self-update path installed, validated, and activated such a staging tree without ever touching the vendored binary modes, so every self-update landed a ripgrep that fails to spawn with EACCES and silently falls back (QwenLM#12668). Chmod the staged vendor/ripgrep/*/rg to 0755 after validation and smoke test, before the activation rename. Best-effort like the source-tree postinstall: platform dirs without a unix rg are skipped, and any other chmod failure only logs — the runtime probe still falls back to a system ripgrep or the JS grep tool, so an update must never fail over this. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-issue-patrol/jmugdo4w20u * fix(cli): heal the ripgrep payload that activation actually adopts Review round 1 follow-ups on the managed npm update heal: - Guard the exec-bit assertion with `process.platform !== 'win32'` instead of calling it vacuous there: libuv synthesizes the mode from the read-only attribute alone, so a writable regular file stats as 0o666 and the assertion cannot pass on Windows. Only the assertion is guarded, so the case still runs there and still covers the win32 `rg.exe` ENOENT swallow. - Heal the version directory that survives the rename collision, and the higher version an existing pointer keeps, not only the staged tree. Both exits of `activateManagedNpmUpdate` previously left `active.json` naming a payload this process never healed, and the collision branch discards the healed one first. The heal stays metadata-only, so a live older session importing that directory is unaffected. - Extend the fixture with the `COPYING` entry the published tree really carries beside the platform directories, and add a non-ENOENT chmod failure (ELOOP via a self-referential symlink) so the skip guard, the ENOENT discrimination and the "must not block the update" contract are all pinned. - Attribute the 0644 normalization to `pnpm pack`, which is what the release publishes with (`.github/scripts/run-release-step.sh` has no `npm publish`). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmugktc5f3t * refactor(cli): drop unobservable chmod-failure reporting from the ripgrep heal The `code !== 'ENOENT'` discrimination existed only to decide whether to call `debugLogger.warn`, and that call cannot reach a sink in the one process that runs it: `runCliEntry` awaits `installManagedNpmUpdate` and returns before route dispatch (cli.ts:520-530), so no `Config` is constructed there and `setDebugLogSession` is never called (config.ts:3104, :5524); `warn` returns early without a session (debugLogger.ts:296-300). Collapse it to a bare catch that states why nothing is logged. The `!entry.isDirectory()` guard goes with it: `readdir` names are all the loop needs now, and `Dirent.isDirectory()` reports a symlinked platform directory as false, so the guard would skip a layout the chmod would have healed. Also drops the test's `vi.hoisted`/`vi.mock` debugLogger spy and its two `warn` assertions, which asserted the mock rather than product behaviour. The COPYING fixture entry stays: the real tree carries it, and the heal now genuinely attempts and swallows `COPYING/rg`. 232 -> 204 added lines. Mutation panel re-run one mutant at a time at this head, zero survivors: dropping the staging heal reds 2 cases, the collision-branch heal 1, the activeDir heal 1; making the chmod catch rethrow reds 4 and the readdir catch rethrow reds 9. 19/19 tests pass, prettier and eslint clean, no type errors in the changed files. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
… fails (QwenLM#12666) * fix(cli): notify when the Linux clipboard tool is found but its query fails On Linux, getLinuxClipboardTool() only probes `command -v wl-paste/xclip`; it never touches the display server. With a stale or dead X server (or a compositor socket that went away) the probe passes while the actual query — xclip -t TARGETS -o or wl-paste --list-types — fails, and the paste was swallowed silently: non-zero exit, spawn error/throw and timeout all collapsed into a bare false, indistinguishable from "no image". Thread onUnavailable into checkClipboardForImage() and getWlPasteImageTypes() and invoke it at each failure exit (non-zero exit, spawn error, spawn throw, timeout), so the caller's unavailability warning fires. A successful query that finds no image still never notifies, so copying text stays quiet, and the no-tool path is unchanged. Refs QwenLM#12505 (finding 1 of 3). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-issue-patrol/jmufyo0er0o * fix(cli): keep an empty Linux clipboard from claiming the native module is broken Linux clipboard tools use one non-zero exit code for both "the display server is dead" and "nothing is on the clipboard", so keying the unavailability notification on the exit code made the image-paste binding report that the native clipboard module could not be loaded whenever the clipboard was simply empty. Capture fd 2 in checkClipboardForImage() and getWlPasteImageTypes(), the same way saveFromCommand() already does so a non-zero exit can be diagnosed rather than merely reported, and skip the notification when stderr identifies the exit as a benign empty clipboard: - xclip: "There is no owner for the ... selection" (xcprint.c errconvsel(), exit(EXIT_FAILURE) when XGetSelectionOwner() is None) - wl-paste 2.x: "Nothing is copied" (bail() is fprintf(stderr) + exit(1)) - wl-paste 1.x: "No selection" (same bail() path) An unclassified non-zero exit still notifies, and the spawn-throw, spawn-error and timeout notifications are left untouched. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmug5t7mp33 * test(cli): make the Linux clipboard failure paths diagnosable Review follow-up to the three Suggestions on this PR: - clipboardUtils.ts: log the exit code (and args) unconditionally when a clipboard query exits non-zero, and log both new timeout paths, mirroring saveFromCommand in the same file. A tool that fails without writing to stderr (xclip exits EXIT_FAILURE silently) notified the user while leaving nothing in the debug log to explain why. - clipboardUtils.ts: bind and log the error in the new catch around spawn('wl-paste'). saveFileWithWlPaste calls getWlPasteImageTypes without an onUnavailable callback, so a synchronous spawn throw was invisible. - clipboardUtils.test.ts: give the two inline BMP fixtures the stderr stream production now reads. Without it, child.stderr.on threw inside the promise executor and both tests passed on saveClipboardImage's catch-all null with nothing past the type query executed. They now assert the unlink of the derived .bmp path (and .png before .bmp), and a createDebugLogger spy pins each new log line. Logging only: no notify condition, return value or cache semantics change. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmugedurp3j * fix(cli): match released xclip empty-clipboard wording, latch the notify Two defects in the Linux clipboard failure classifier, both reported against d4439fc. The only xclip marker was 'no owner for the', which comes from errconvsel() in unreleased xclip git master (xcprint.c:136, called from xclip.c:778). Released 0.13 -- the version Debian, Ubuntu and Fedora package -- has no errconvsel() at all: a -t TARGETS -o query on a selection nobody owns takes the XCLIB_XCOUT_BAD_TARGET branch that has no fallback left, prints "Error: target TARGETS not available" and returns EXIT_FAILURE (xclip.c:468). No marker matched that, so a routine empty clipboard fell through to onUnavailable and rendered the "native clipboard module could not be loaded / reinstall Qwen Code" notice, which cannot be true on the Linux branch because it never loads that module. main returned false silently on this exact input, so it was a regression, and it spent clipboardUnavailableShownRef -- latched once per session and never reset -- so the genuine dead-X-server failure this PR exists to report could not be reported again for the rest of that run. Both spellings are now markers, and the doc comment says which xclip each one belongs to. The unconditional exit-code log copied saveFromCommand's line but not the `if (resolved) return;` latch that makes that line safe there. Node delivers `close` after a spawn `error` (carrying the negated errno) and after a timeout `kill()` (with code === null), so the close handler re-entered its non-zero branch for a query that had already been reported: it recorded `exited with code -2` for a binary that never started, `exited with code null` immediately after the timeout line, and fired onUnavailable a second time for one failure. Added the same settle latch to checkClipboardForImage and to getWlPasteImageTypes, and logged the spawn errno in both error handlers, since with the fabricated exit code suppressed that is otherwise the only trace a spawn failure leaves. The failure paths still do not cache: the latch returns before `cachedWlPasteImageTypes = types`. The two fixtures now reproduce Node's real event order -- createSpawnErrorChild emits close(-2) after error, and createHangingChild's kill emits close(null, 'SIGTERM') -- so the existing toHaveBeenCalledOnce assertions witness the latch instead of a sequence production never produces. 61 tests pass (60 before, +1 for the released xclip wording). Every new or strengthened assertion was mutation-checked on its own: removing the marker reds the new test with "expected spy to not be called at all, but actually been called 1 times"; removing either function's latch reds exactly its three witnesses ("expected spy to be called once, but got 2 times" plus the 'exited with code' negative) while the genuine exit-code, empty-clipboard and dead-display tests stay green. cli typecheck reports no clipboardUtils error. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmugktc5f3t * test(cli): fold the duplicated timeout cases, correct the marker provenance Cleanup for the two round-2 Suggestions still open at 99289af. Net -21 lines; no production behavior change beyond comments. R2-2: the two `records the timeout when the query never answers` cases re-ran the exact scenario the two `notifies when the query times out` cases already cover -- same env stubbing, same probe result, same createHangingChild(), same call resolving to false -- and differed only in which callback they asserted on. Each spent a real five seconds against the module-private PROCESS_TIMEOUT_MS, so the pair cost ten seconds on every local run and every CI shard that picks up this file. Their witnesses are folded into the two existing cases, which now assert a superset: the timeout log line, the `exited with code` negative that catches the fabricated exit code arriving from kill()'s close(null, 'SIGTERM'), and notify-once. Test count 61 -> 59, five-second cases 4 -> 2, file duration 22.9s -> 18.0s. Driving the remaining two with fake timers would remove the last ten seconds, but that is test infrastructure rather than a defect, so it is left as follow-up. R2-3: the BMP witness comment read as confirming the python3 conversion path is now pinned. It is not. fs.open is unmocked while mkdir is a no-op, so saveFromCommand fails at its O_EXCL open before spawning, python3 never runs, and the unlink counted there is the save-failure tail -- which also leaves the fixture's callCount 2/3 arms unreachable. The comment now says so. Covering the conversion catch and deleting those dead arms are pre-existing on the base commit, so they stay follow-up rather than widening this PR. Also corrects the wl-paste provenance: "Nothing is copied" arrives in wl-clipboard v2.2.0 (src/wl-paste.c:252), not at the 2.x boundary -- v2.0.0 and v2.1.0 still print "No selection" (src/wl-paste.c:187). Both markers were already load-bearing, just for a different version reason than stated, which is the same master-versus-released confusion R2-1 was about. The citation now lives once on EMPTY_CLIPBOARD_STDERR_MARKERS instead of being restated verbatim in the test file; the duplication is what let the two copies drift. Each folded witness was re-mutation-checked individually after the move: removing the released-xclip marker reds its case with "expected spy to not be called at all, but actually been called 1 times", and removing either function's close latch or timer latch reds exactly its case with "expected spy to be called once, but got 2 times". 59 tests pass; eslint, prettier --check and cli typecheck report nothing on either changed file. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(clipboard): pin the four diagnostic logs R3-1 named as unasserted clipboardUtils.ts logs four diagnostics on the Linux clipboard failure paths that the new tests executed but never asserted, so reverting child.on('error', (err) => { to the pre-PR child.on('error', () => { dropped the binding and the log together and still shipped green. Those logs are load bearing. The settle latch suppresses the close handler's fabricated exit code, so the errno is a spawn failure's only trace, and the two stderr echoes are the only record of the wording isEmptyClipboardError() classified -- without them a failing query reads just "xclip exited with code 1. Args: ..." and the classification decision is no longer auditable. Until QwenLM#12504 fixes the onUnavailable text, the debug log is also the only place the real failure reason is recorded anywhere. One assertion per site, added to the tests that already reach them, following the toHaveBeenCalledWith convention the other three logs in this PR already use. The wl-paste async-error assertion targets .debug, not .error, because the synchronous-throw arm logs the byte-identical message at the other level and is pinned separately; the two stderr expectations omit the fixtures' trailing newline because both production sites call .trim(). Each assertion was mutation-checked individually: deleting or reverting its own site now reds exactly one test (1 failed | 58) with no collateral, and the production file is restored byte-identical afterwards. Test-only change -- no log statement was removed, reworded or consolidated. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmugtdz9149 --------- Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…k aggregation (QwenLM#12689) When multiple PreToolUse hooks match one tool call, the documented hookSpecificOutput.permissionDecision fell into the generic "later values win" merge branch, so the last hook in config order overrode any earlier deny and the tool ran. Rank the field during aggregation (deny > ask > allow) with the reason following the winning rank (same-rank reasons concatenate), leaving top-level decision OR-logic and last-wins for other hookSpecificOutput fields unchanged. Fixes QwenLM#12683 Patrol-Run: qwen-issue-patrol/jmugqj3jg10 Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Add the contract for W0a-2, the second W0 slice of QwenLM#12380: how a Runtime Broker and a Managed Runtime worker agree on managed-context/1, boot document v2 and ready record v2, attestation v3, and the context installation request with its receipt. Nothing is wired yet; W0c mounts the routes and writes boot v2. - Boot v2 carries the Workspace binding and the mount root but no Session context, since a Workspace-isolated Runtime is shared. Every field that a response repeats is bounded, so every record fits within its 16 KiB limit. - A Session installs its ContextBinding with an idempotent request. The worker checks the shape, the binding's digest, the Workspace part, and then operation and Session reuse. A refused request records nothing. - A shared JSON schema and fixture file pins every closed key set, field rule, route, error classification and the check order. An implementation independent of both languages generated the expected values. - managed-context-envelope.ts validates boot documents, ready records, attestation requests and installation requests, and builds ready records, attestation responses and receipts. It reuses the W0a checks and digest, which managed-workspace-binding.ts now exports. - A Java conformance test in runtime-broker pins the key sets, routes, constants and error table, and recomputes every installation digest with ContextBinding. - A bilingual design document. The W0a document and the runtime-broker README and QWEN.md now point to it. Refs QwenLM#12380
* feat(sdk-java): project Runtime tool replies onto the broker contract HttpRuntimeTransport now implements RuntimeTransport. Status and cancel answers keep only state and a settled result, and session verbs that the v2 tool contract does not define fail closed. Co-authored-by: jinye <doudouOUC@users.noreply.github.com> * feat(sdk-java): adopt local Runtime processes by ready record The local-process provisioner now reports a durable kind, boots from the persisted seed, and reconciles a restored lease by attesting it again. A process that no longer answers is not found instead of guessed ready. Co-authored-by: jinye <doudouOUC@users.noreply.github.com> * feat(serve): add the hosted harness profile and broker tool path qwen serve --profile hosted-harness now requires a loopback bind, a harness token, and the Runtime Broker origin, token, and capability digest. The broker client speaks the merged execution routes, and the local-process provisioner adopts workers by ready record. Co-authored-by: jinye <doudouOUC@users.noreply.github.com> * fix(serve): guard incomplete hosted runtime foundations * fix(serve): close hosted runtime review gaps * test(core): make exited PID assertion deterministic --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: jinye <doudouOUC@users.noreply.github.com> Co-authored-by: jinye.djy <jinye.djy@U-Y37CF7G3-1943.local> Co-authored-by: Shaojin Wen <szujobs@gmail.com>
Bring the standalone managed-agent server, its embedded broker listener, and the WebShell Java panel onto the current broker contract. Legacy daemon sessions stay available beside the new path. Co-authored-by: jinye <doudouOUC@users.noreply.github.com>
72c459e to
b08af8f
Compare
What this PR does
This is one review block split out of preview QwenLM#12358, rebased onto current QwenLM main (
90232f0eb, published here ascursor/upstream-main-3df3). It adds the standalone Spring control plane (packages/sdk-java/managed-agent-server) and the WebShell Java Managed panel, while leaving legacy daemon sessions in place.The server embeds the merged Runtime Broker. A new private HTTP listener speaks acquire, control, release, create, start, cancel, and resolve. Create and prepare both call the merged
createExecutionpath, which records the call and drives dispatch. Resolve callsreconcileExecutionand does not treat the caller's resolution label as a result. Static placements return a fixed lease. Local-process placements launch the merged stdin attestation worker.The two adapter commits already opened as #3 and #4 are included here because the server cannot construct the merged broker without them.
Why it's needed
Issue QwenLM#12380 asks for a Hosted Harness control plane in front of
qwen serve, with one Session ID and a post-commit event path to the browser. Reviewing that surface inside the 500-file preview is not practical. This block is the Java control plane plus the dual-path WebShell.Reviewer Test Plan
How to verify
From
packages/sdk-java, installqwencodeandruntime-brokerwith-Dgpg.skip=true, then inmanaged-agent-serverrunmvn -Dgpg.skip=true test checkstyle:check. Expect the H2 integration tests and the embedded-broker listener test to pass. Checkstyle usespackages/sdk-java/qwencode/checkstyle.xml.WebShell unit tests were not run in this environment because workspace
node_modulesis not installed.Evidence (Before & After)
N/A for screenshots. Linux JDK 21, Maven 3.8.7:
managed-agent-servermvn test checkstyle:checkpassed after the shared session-store fixture was added. BrokerHttpRuntimeTransportTestandLocalProcessRuntimeProvisionerTestpassed on the included adapter commits.Tested on
Environment (optional)
JDK 21.0.10, Maven 3.8.7.
mvn installforqwencodeneeds-Dgpg.skip=truebecause the gpg plugin has no signing key here.Risk & Scope
preparestarts dispatch immediately, same ascreate.scripts/run-managed-agent-server-e2e.tsis included and is not wired into CI.QwenHostedHarnessConnector.loadOrCreatestill loads before create, so a brand-new stored session whose load returns 500ownership was not verified(instead of 404) never reaches create./internal/managed-session-store/v1/is gated by a client-supplied writer token and tenant header, not by a service credential. The Java session store ships in this block because the connector calls it; the TypeScript journal, authority, and failover tests are a separate review block.0.1.0-alpha, matching main, not the preview's0.1.2-alpha. This pull request is ondoudouOUC/qwen-codeand cannot merge intoQwenLM/qwen-codedirectly.Design notes:
docs/design/2026-09-19-managed-agent-spring-server.mdand.zh-CN.md,docs/design/2026-09-20-managed-agent-dual-path-web-shell.mdand.zh-CN.md,docs/design/2026-09-20-managed-agent-storage-event-architecture.mdand.zh-CN.md,docs/design/2026-09-21-managed-session-durable-store.mdand.zh-CN.md.Linked Issues
Related to QwenLM#12380. Split from QwenLM#12358. Does not close either.
中文说明
这是从预览 QwenLM#12358 拆出的一整块评审,基线是当前 QwenLM main(
90232f0eb,在本仓库记为cursor/upstream-main-3df3)。它加入独立的 Spring 控制面和 WebShell 的 Java Managed 面板,原有 daemon 会话仍然保留。服务内嵌的是已经合并的 Runtime Broker。新增的私有 HTTP 监听覆盖 acquire、control、release、create、start、cancel、resolve。create 和 prepare 都走合并后的
createExecution,会记下调用并开始派发。resolve 调用reconcileExecution,不会把调用方传来的 resolution 当成结果。static 供给器返回固定租约。local-process 拉起的是合并后的 stdin attestation worker。#3 和 #4 里的两笔适配提交包含在本块中,否则服务无法按合并后的构造器创建 Broker。
为什么需要
QwenLM#12380 需要 Hosted Harness 控制面位于
qwen serve之前,使用同一个 Session ID,并在提交后再把事件送到浏览器。整份预览有五百多个文件,不适合一次评审。这一块只看 Java 控制面和双路径 WebShell。如何验证
在
packages/sdk-java用-Dgpg.skip=true安装qwencode和runtime-broker,然后在managed-agent-server执行mvn -Dgpg.skip=true test checkstyle:check。H2 集成测试和内嵌 Broker 监听测试应当通过。本环境没有安装 workspace 的node_modules,WebShell 单测没有跑。风险与范围
嵌入层按合并后的 Broker 改写,没有用预览里那份 Broker 实现覆盖主线。Harness 级 drain 只在本进程把会话标成关闭,不会拆掉 worker。本块拒绝 Kubernetes 供给器。local-process 若配置了额外环境变量会直接拒绝,因为合并后的供给器没有环境参数。
prepare会立刻派发,和create相同。QwenHostedHarnessConnector.loadOrCreate仍然先 load 再 create。存储开启时,全新会话如果 load 返回 500ownership was not verified而不是 404,就不会进入 create。/internal/managed-session-store/v1/只校验调用方自己带的 writer token 和租户头,没有服务级凭证。Java 会话存储因为连接器会调用它,所以留在本块;TypeScript 的 journal、authority 和 failover 测试另开一块评审。本 PR 在
doudouOUC/qwen-code,不能直接合并进QwenLM/qwen-code。关联 QwenLM#12380,从 QwenLM#12358 拆出,不关闭这两个单子。