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: ae96eeeb5f
ℹ️ 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".
| export function isManagedSessionTranscriptSync( | ||
| filePath: string, | ||
| scratchBuffer?: Buffer, | ||
| ): boolean { |
There was a problem hiding this comment.
Block the legacy rename path for Managed sessions
Wire this Managed-session detector into SessionService.renameSessionInternal before it appends a legacy custom_title record. A repo-wide search shows that the new detector has no production caller, so renaming a local Managed session still appends that record after the Managed header; scanManagedSessionJournal then rejects the unknown post-header subtype, making the session impossible to reopen.
Useful? React with 👍 / 👎.
| export function managedSessionResourceRoot( | ||
| runtimeBaseDir: string, | ||
| sessionId: string, | ||
| ): string { | ||
| if (!isSinglePathSegment(sessionId)) { | ||
| throw new Error('sessionId must be a single path segment.'); | ||
| } | ||
| return path.join(runtimeBaseDir, 'resources', sessionId); |
There was a problem hiding this comment.
Remove Managed resources when deleting a local session
Add this resource root to the existing SessionService.removeSession cleanup. Currently its only production consumer is LocalManagedSessionResourceStore; the deletion path removes the transcript and legacy sidecars but never removes runtime/resources/<sessionId>, so deleting a Managed session leaves all message, checkpoint, and metadata bodies—including potentially sensitive content—on disk indefinitely.
Useful? React with 👍 / 👎.
| } catch (cause) { | ||
| // Records may already be on disk, so the sequences this transaction | ||
| // claimed are spent whether or not the marker landed. | ||
| this.writeFailure = | ||
| cause instanceof Error ? cause : new Error(String(cause)); | ||
| throw cause; |
There was a problem hiding this comment.
Preserve retryability after an ambiguous remote commit
Do not permanently poison the authority for every appendTransaction error when the selected journal is the HTTP atomic store. If the server commits /transactions:commit but its response is lost, the client throws here and writeFailure prevents the same command from being retried, even though the HTTP protocol and receipt handling are explicitly idempotent for this ambiguity; the live Harness becomes unusable until takeover or restart.
Useful? React with 👍 / 👎.
| export function readManagedSessionTitleInfoSync( | ||
| filePath: string, | ||
| runtimeBaseDir: string, | ||
| scratchBuffer?: Buffer, | ||
| ): { title?: string; source?: 'auto' | 'manual' } | undefined { | ||
| const found = readManagedDomainBodySync( | ||
| filePath, | ||
| runtimeBaseDir, | ||
| MANAGED_METADATA_MARKER, | ||
| scratchBuffer, | ||
| ); |
There was a problem hiding this comment.
Use the Managed title reader in session listings
Route SessionService title reads through this helper for Managed transcripts. At this commit the only production caller is the restore projection, while SessionService.getSessionTitleInfo and session listing still call readSessionTitleInfoFromFileSync, which searches for legacy custom_title records that the Managed sink deliberately does not write; consequently a successfully committed Managed rename is absent from the session picker and title APIs.
Useful? React with 👍 / 👎.
| operation: 'commitMessage', | ||
| commandId: `recorder:${record.uuid}`, | ||
| sessionKey: this.authority.sessionHeader.sessionKey, | ||
| contentDigest: this.authority.sessionHeader.definitionRef.digest, | ||
| }, |
There was a problem hiding this comment.
Digest each carried record for idempotency
Derive contentDigest from the actual record rather than the session definition. Every ordinary projected message currently uses the same digest, so if recorder:<uuid> is retried with changed content, LocalManagedSessionAuthority.commit treats it as an exact replay and silently returns the original receipt instead of raising the intended idempotency conflict; the title and other domain-record paths repeat the same pattern.
AGENTS.md reference: AGENTS.md:L261-L265
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>
…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>
3fad955 to
9fdf1e3
Compare
…nLM#12692) * feat(managed-agent): add Spring control plane and dual-path WebShell 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> * fix(managed-agent): address review defects and wire server CI * fix(managed-agent): preserve case-sensitive store identities * fix(managed-agent): bind locally and return 404 for missing routes * codex: address PR review feedback (QwenLM#12692) --------- 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>
QwenLM#12308) * feat(serve): support model and reasoning selection at session creation * fix(serve): allow model-only session startup configuration * test(serve): include startup capability in registry expectations * fix(serve): roll back rejected standalone startup * fix(serve): make definite startup rejections retryable and honest - Map the ACP internalError wire shape ({code: -32603, data.details}) produced when switchModel refuses an unregistered model to startup_config_rejected on the startup-config call path, so the ordinary route answers 422 instead of 500 and the standalone path takes its definite rollback branch. - Roll back the persisted recording of a caller-supplied sessionId on POST /session after a definite startup rejection, so a retry of the same id is no longer answered with 409 session_id_conflict; uncertain failures keep the recording. - Publish auto in the OpenAPI approvalMode enum and pin it against APPROVAL_MODES; narrow the 422/protocol/design cleanup wording to match the implemented best-effort semantics; restore the sessionId in the new rollback warning. * fix(serve): keep uncertain startup faults uncertain and roll back ACP recordings (QwenLM#12308) - session-startup-config: drop the -32603 arm so child-side internal errors (auth, credential, timeout, transport) stay uncertain instead of a false definite startup_config_rejected; the ACP child now maps only its two caller-caused switchModel refusals (unknown model for the auth type, media-only primary) to invalid params, so definite rejections ride -32602 alone. - dispatch: roll back the persisted recording of a caller-supplied sessionId on the ACP session/new transports after a definite startup rejection, mirroring the REST route; uncertain outcomes keep it. - routes/session: warn once when the recording rollback is refused or fails instead of silently discarding the boolean outcome. - standalone-session-service: extract the shared rollback sequence into one helper used by both the startup-config and model_selection_failed branches. - openapi: qualify the 422 close/rollback as best-effort and replace the unconfirmable status-view pointer with the self-certifying retry check. * fix(serve): name the rejected session in startup rollback and align ACP error codes (QwenLM#12308) - SessionStartupConfigError carries an optional sessionId so both rollback gates (REST route, ACP dispatcher) roll back the rejected session's recording even when the caller never supplied an id or the child did not honor it; a daemon-generated id no longer leaves a listed, resumable phantom after a definite 422. - A refused ACP recording rollback now logs the same inconclusive warning the REST route emits instead of dropping the cleanup result. - startup_config_rejected maps to JSON-RPC -32602 (caller input), with data.httpStatus keeping the REST-equivalent 422; SDK transports key on httpStatus, so client behavior is unchanged. - SDK throws DaemonStartupConfigError (TypeError with code 'invalid_startup_config') for pre-transport startup rejections. - Docs: modelApplied is documented as spawn-scoped again (an attach omits it or reports the spawn owner's outcome). - Tests: daemon-generated-id rollback pins on both transports, a refused-rollback log pin, a service-boundary invalid-config guard pin, full reasoning-selection acceptance coverage, whitespace trim pins, and a real-ModelsConfig classifier coupling test. * test(sdk): keep the message assertion beside the stable code on startup rejections (QwenLM#12308) * fix(serve): fail closed on control-character startup model ids (QwenLM#12308) A modelServiceId carrying a newline slipped past parseSessionStartupConfig and produced a core refusal message the single-line classifier regexes could not match, so a definite caller-caused rejection degraded to an internal error and skipped the recording rollback. Reject control characters at the parse boundary and make isCallerCausedModelRefusal newline-tolerant; align the SDK and integration-guide modelApplied presence rule with the spawn-scoped wording the bridge and OpenAPI already publish. * docs(sdk): name both transport error-code locations in DaemonStartupConfigError JSDoc (QwenLM#12308) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Bring the session authority, JSONL journal, HTTP store client, checkpoint, and continuation tests over as their own review block. The Java store they call stays with the Spring control plane. Co-authored-by: jinye <doudouOUC@users.noreply.github.com>
c2844e6 to
1443597
Compare
What this PR does
This is the durable Session and failover review block split out of preview QwenLM#12358, on current QwenLM main (
90232f0eb, published here ascursor/upstream-main-3df3).It adds the TypeScript session authority, local JSONL journal, HTTP store client, harness checkpoint, prompt inbox, activation fence, dispatch gate, and the tests that cover continuation and store conflicts. Transcript reading learns the managed execution engine, and session storage gains the managed resource root and title helpers. The shared contract fixture is included so the Java and TypeScript sides can be compared against the same bytes.
The Java session store, schema migrations, and writer-token routes are not in this diff. They ship with the Spring control plane, because the connector calls them.
Why it's needed
Issue QwenLM#12380 stage G is a durable session that can fail over without mixing that storage into the hosted tool loop or the WebShell panel. The preview kept the journal, the HTTP client, and the Java store in one tree. This block is the TypeScript half.
Reviewer Test Plan
How to verify
With workspace dependencies installed, run:
npx vitest run packages/core/src/managed-runtime/managed-session-authority.test.ts packages/core/src/managed-runtime/http-managed-session-store.test.ts packages/core/src/managed-runtime/managed-harness-checkpoint.test.ts packages/core/src/managed-runtime/managed-session-store-contract.test.ts packages/core/src/managed-runtime/local-jsonl-managed-session-journal-store.test.tsThis workspace has no
node_modules, so those tests were not executed here.Evidence (Before & After)
N/A. No runtime was started for this block.
Tested on
Environment (optional)
Node dependencies are not installed in this workspace.
Risk & Scope
http-managed-session-store.tstalks to/internal/managed-session-store/v1/. That Java route, in the Spring block, checks a client-supplied writer token and tenant header. It does not check a service credential, so a caller who can reach the port can attempt lease and journal operations. That gap stays on the Java review, not fixed here.--session-failover,--inflight-failover,--continuation-failover) is implemented byscripts/run-managed-agent-server-e2e.tsin the Spring block and is not run in CI. This diff does not change the broker, the hosted profile, or WebShell. The tool digest helper is included because the journal and HTTP client hash requests with it; the tool executor is not.doudouOUC/qwen-codeand cannot merge intoQwenLM/qwen-codedirectly.Design notes:
docs/design/2026-09-21-managed-session-durable-store.mdand.zh-CN.md. The same pair is also in the Spring control-plane review.Linked Issues
Related to QwenLM#12380. Split from QwenLM#12358. Does not close either.
中文说明
这是从预览 QwenLM#12358 拆出的 Durable Session 与 failover 评审块,基线是当前 QwenLM main(
90232f0eb,在本仓库记为cursor/upstream-main-3df3)。它加入 TypeScript 的会话权威、本地 JSONL journal、HTTP 存储客户端、Harness checkpoint、prompt inbox、activation fence、dispatch gate,以及覆盖续跑和存储冲突的测试。转录读取会识别 managed 执行引擎,会话存储补上 managed 资源根目录和标题辅助函数。共享契约夹具也在里面,Java 和 TypeScript 可以对同一组字节。
Java 会话存储、schema 迁移和 writer token 路由不在本 diff。它们跟 Spring 控制面走,因为连接器会调用它们。
为什么需要
QwenLM#12380 的阶段 G 是可故障转移的持久会话,不应和 Hosted 工具闭环或 WebShell 面板混在一次评审里。预览把 journal、HTTP 客户端和 Java 存储放在同一棵树里。这一块是 TypeScript 这一半。
风险与范围
http-managed-session-store.ts访问/internal/managed-session-store/v1/。Spring 那一块里的 Java 路由只校验调用方自己带的 writer token 和租户头,没有服务级凭证。能打到该端口的调用方就可以尝试租约和 journal 操作。这个缺口留在 Java 评审里,本块没有改它。进程级 failover(
--session-failover、--inflight-failover、--continuation-failover)在 Spring 块的scripts/run-managed-agent-server-e2e.ts里,而且没有进 CI。本 diff 不改 Broker、Hosted profile 或 WebShell。工具 digest 辅助函数在这里,是因为 journal 和 HTTP 客户端用它给请求做哈希;工具执行器不在这里。本环境没有
node_modules,这些测试没有跑。本 PR 在doudouOUC/qwen-code,不能直接合并进QwenLM/qwen-code。关联 QwenLM#12380,从 QwenLM#12358 拆出,不关闭这两个单子。