mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
406258d66abc2e2bc1073509e625c0bb000ccc4c
11966
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
406258d66a |
fix(core): headless project-hook trust requires the folder-trust feature (#16264)
* fix(core): headless hook trust requires the folder-trust feature The reporter PoC runs headless (--output-format stream-json with a one-shot prompt), so the unconditional headless exemption left the reported execution path open on a fresh default environment. Require the operator to have enabled security.folderTrust.enabled before the workspace choice counts as implicit trust; with the feature off, project hooks are withheld in every mode. Embedders enable the feature in their managed home settings and keep working via their trust store. Verified locally against the reporter scenario: default fresh home -> marker absent; feature on + trust-store grant -> marker written. Refs #14746 Co-authored-by: Qwen-Coder <[email protected]> * refactor(core): unify the project-hook gate on explicit trust The headless exemption branch never changed an observable outcome: the CLI settings layer already drops workspace settings for an undecided folder once the feature is enabled, and with the feature off the explicit predicate is false anyway. Replace both branches with the single explicit-trust rule so interactive and non-interactive callers share one contract. Behavior is unchanged; verified against the reporter PoC (default fresh home withheld, trusted path executes). Co-authored-by: Qwen-Coder <[email protected]> * fix(cli): address review findings on the unified hook trust gate R1-1: the headless feature-off test wrote folderTrust into workspace settings, where WORKSPACE_RESTRICTED_SETTINGS strips it — the rig user scope stayed enabled and the test never reached the named gate. Write the feature-off decision into the user-scope home file instead. R1-2: the docs sentence overclaimed; IDE-granted trust loads hooks with the feature off. State the predicate as shipped. R1-3: headless withholding was silent for the newly gated population. Emit one stderr line from the non-interactive entry and a debug-log warn from the registry when the trust gate is why the scope is absent (getTrustWithheldProjectHooks, diagnostics only). Verified: reporter PoC withheld with the stderr line present; core 1052 passed; tool-hook integration 14 passed. Co-authored-by: Qwen-Coder <[email protected]> * feat(web-shell): surface trust-withheld hooks in the workspace overview The hooks listing already marks why a project hook is disabled, but the ACP status serializer dropped the reason, so the web-shell overview only showed a count while trust-withheld hooks silently did not run. Carry disabledReason through ServeHookEntry/DaemonHookEntry (optional, older daemons unaffected), count untrusted hooks in the summary, flag the facet, and name the remedy in the tooltip (en + zh). Co-authored-by: Qwen-Coder <[email protected]> * test(web-shell): pin the trust-withheld hooks tooltip wording Component-level witness for the untrusted-hooks facet: the row shows the withheld count and the title names the remedy. The sidebar hooks facet is opt-in for embedding hosts, so the jsdom suite is the reachability-independent pin. Co-authored-by: Qwen-Coder <[email protected]> * test(cli): align fixtures with the surfaced hook state The workspace-hooks status test now expects the disabledReason the serializer carries, and the headless-warnings config stub provides the new getTrustWithheldProjectHooks accessor. Co-authored-by: Qwen-Coder <[email protected]> * fix(cli): tolerate partial config stubs in the headless hook notice The resumed-transcript and stream-json housekeeping tests use minimal config stubs; optional-chain the two accessors the notice reads, and provide the accessor on the remaining isInteractive stubs. All 124 llm.test.tsx cases pass. Co-authored-by: Qwen-Coder <[email protected]> * fix(hooks): make the withheld-hooks surfaces reachable and equivalent Review findings on the status surfaces: the listing never carried disabledReason untrusted for settings project hooks (the trust gate keeps them out of the registry, and only agent/session rows were trustGated), so the web-shell hint was unreachable; buildHooksListing now appends the diagnostics block as disabled (untrusted) rows, with a real-listing test. The headless stderr notice now mirrors the TUI predicate (raw workspace hooks non-empty, exclude disableAllHooks and safe/bare), covering the feature-on undecided case the Config accessor cannot see. The facet value stays language-neutral; the localized withheld count and remedy live in the tooltip. Core 1035, llm 124, web-shell sidebar 51 tests pass. Co-authored-by: Qwen-Coder <[email protected]> * test(web-shell): browser witness for the withheld-hooks status surface Real Chromium e2e: open Daemon Status via the rail More popover, switch to Diagnostics, and assert the workspace.hooks section renders with the withheld count from a report shaped like the real daemon output. Screenshot retained as test output. Co-authored-by: Qwen-Coder <[email protected]> * fix(hooks): fold the deferred approval follow-ups into this PR Per review direction, land the #16295 items here instead of a follow-up: - Explicit projectHooksApproved state: a stored approval keeps execution authority inside a command-line --yolo session (previously the trust gate withheld it because the pending flag is undefined while exempt). The flag never doubles as nothing-to-approve; mid-session decisions update it. Tests pin yolo+approved executes, yolo+unapproved withholds. - Display-only raw workspace block: with the feature on and the folder untrusted the settings layer drops the workspace scope, so the listing could not show the withheld hooks. projectHooksDisplayOnly feeds only getTrustWithheldProjectHooks, never the registry; test pins both halves. - Variable-source wording: the dialog hint now says environment (project .env/settings.env or shell), not only the approved project env. Per-hook approval granularity stays out: it is a feature iteration, not a correctness fix. Core 1017+1053, cli config 565, approvals 50, llm 127 pass. Co-authored-by: Qwen-Coder <[email protected]> * style: prettier the reworded dialog hint Co-authored-by: Qwen-Coder <[email protected]> * fix(cli): /hooks list shows withheld project hooks in ACP/web-shell The non-interactive /hooks list read only the registry, so an ACP session (web-shell) with unapproved project hooks answered \"No hooks configured\" — the user could not see what was withheld or why. The list now includes the approval/trust diagnostics blocks, marked disabled (not approved / rejected / folder not trusted). Verified against a real ACP session: the withheld SessionStart hook is listed with its reason. Tests pin all three reasons. Co-authored-by: Qwen-Coder <[email protected]> * feat(web-shell): explicit project-hooks approval prompt for withheld hooks Web Shell previously had no interactive surface for the project-hooks approval gate: users had to run /hooks manually. The daemon now reports the pending decision (with sanitized hook lines) on /workspace/hooks, including on a cold daemon and when folder trust withholds the workspace scope, and a dedicated POST /workspace/hooks/approve route (plus the workspace-qualified twin) records the same hash-bound decision the TUI dialog writes. The Web Shell renders an Approve / Keep-disabled banner from that state. This is deliberately not session/request_permission: automation clients auto-answer permission prompts, a trust decision must be an explicit user act, and approving never grants folder trust. Co-authored-by: Qwen-Coder <[email protected]> * refactor(web-shell): render hooks approval with the shared ToolApproval card Replace the custom full-width alert banner with the existing ToolApproval card in the standard approval slot above the composer, so the hooks trust decision looks and behaves like every other approval: same panel, option list, keyboard handling, and safe-default focus. The card supplies a project_hooks request with decision-specific option labels; ToolApproval localizes its heading and question for that tool name. Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): hooks approval probe reads once and polls only while pending The approval card polled /workspace/hooks unconditionally, adding background metadata reads the workspace-overview quiet-period contract forbids. Read once per mount/workspace/settings change, poll only while a decision is pending, and let the E2E quiet-period test settle the initial probe before counting sidebar traffic. Co-authored-by: Qwen-Coder <[email protected]> * fix(hooks): apply the persisted decision once and to every live session Local review + ponytail over the PR diff: - The ACP child no longer re-derives and re-writes the approval hash on the approve control. The daemon persists the decision for the exact block the user reviewed; a second write could bind it to a block that changed since the review. The child only applies the decision, to every live session config, not just the bootstrap one. - The raw-workspace review subject hashes the project env files regardless of current folder trust, so a decision recorded while untrusted still matches after the folder is trusted. - The headless trust warning no longer doubles with the approval-gate warning for a pending block; the registry debug-log duplicate and its interface member are removed (the stderr line and listings already report the withheld state). - Drop dead test stubs for an accessor llm.tsx never reads, a redundant type member, and a stray blank-line deletion. Co-authored-by: Qwen-Coder <[email protected]> * fix(hooks): scope the approve decision to the admission-bound workspace The per-session fan-out matched every live config in the child. Key it on the admission-bound workspace root (the same root the settings handlers use) against the request's cwd instead: session/cd moves the live target dir, and a decision recorded for one workspace must not clear or set the gate on a session admitted under another. Regression test covers same-workspace, cd-moved, and foreign-workspace configs. Co-authored-by: Qwen-Coder <[email protected]> * fix(cli): bind the project-hooks approval to the reviewed block The daemon's approve route re-read .qwen/settings.json at click time and persisted the hash of that fresh read, while the ACP child applied the decision to the block it rendered at startup. A file change inside that window (git pull, another session, the workspace's own agent) recorded an approval for hooks the user never saw, and left the card pending because the stored hash matched neither side. The child now persists against the same read that produced the status cell it rendered, so the reviewed block and the recorded block are the same object. The daemon only falls back to its own read when no child answered, which is the cold-daemon case where that read is what the client saw. Also unbreaks the web-shell lane: App.test.tsx's daemon-react-sdk mock had no useOptionalWorkspace export, so every App render threw and 1320 tests failed; and the workspace-overview facet counts no longer include the per-mount /hooks probe, which broke the exact facet-set assertion. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw2kfno0dl * fix(cli): explicit folder trust alone runs project hooks The startup gate demanded a stored approval even when the folder was explicitly trusted, contradicting the documented either-or authority: core's readProjectHooks accepts approval OR explicit trust, but the pending flag short-circuited the accessor before trust was consulted. A platform that pre-trusts its managed directories (folderTrust enabled plus a TRUST_FOLDER/TRUST_PARENT entry) saw every project hook withheld headless. Clear the pending gate for a trusted folder unless the exact block was explicitly rejected — a recorded 'no' still wins over trust. Verified headless on a real fixture: fresh home withholds; trust config alone fires the hook; a rejected block stays withheld in the same trusted folder. Gate unit tests cover pending/rejected/approved under explicit trust. Co-authored-by: Qwen-Coder <[email protected]> * fix(hooks): bind the approval decision to the rendered block hash end to end The status cell now publishes pendingApprovalHash, the Web Shell card echoes it with the decision, and both the daemon route and the ACP child verify it: the daemon rejects a decision whose hash names a block other than the current file (409 project_hooks_changed, nothing persisted), and the child applies a hash-bound decision only to configs holding that exact startup subject (projectHooksApprovalSubject), leaving any config with a different block pending. Closes the cold-daemon re-read window: an approval can no longer bind commands the user never saw. Verified on a real daemon: render hash -> edit the hook file -> POST with the rendered hash returns 409 and persists nothing; re-render -> approve with the fresh hash persists and enables the hook; a real browser session then fires it. Card tests 5/5 (including hash echo), approve-control tests 2/2, gate tests 20/20, build + bundle clean. Co-authored-by: Qwen-Coder <[email protected]> * fix(hooks): close the remaining approval-gate review findings - Escape on the Web Shell trust card defers without recording a decision (TUI semantics): ToolApproval gains escapeDismisses/onDismiss, and the card hides until the block hash changes instead of persisting a rejection for a habitual keypress. - The card's options are *_once kinds so the shared safe-default logic pre-selects the reject side; a stray Enter on the security gate can never grant hook execution. Decision-specific labels stay via the option-id i18n keys. - The hooks-trust card reports into approvalOverlayActive, so the composer is dormant while the card owns the keyboard, exactly like a tool approval. - Under command-line YOLO, replacing the hooks block drops the old approval authority: an edited block cannot execute on the previous block's approval while the gate is exempt. - The trust-withheld listing surfaces the display-only block even when the empty execution block reads the gate as cleared (feature on, folder untrusted) — the state the copy exists for. Tests: core config + hooks-listing 1055/1055, hooks reload 38/38, ToolApproval + card 99/99 including Escape-defer and reject-default regressions; build + bundle clean. Co-authored-by: Qwen-Coder <[email protected]> * chore: record merge provenance The main merge ( |
||
|
|
92bed7eeae |
perf(memory): move the main session's auto-memory guidance into a bundled skill (#16303)
* perf(memory): move the main session's auto-memory guidance into a bundled skill The main session's `# auto memory` system-prompt section was 16,140 of 35,792 characters on every request, while the main model writes memories on only a few turns. The session now gets a resident policy (directories and routing, one line per type, what not to save, the two save steps with the frontmatter template and category names, deduplication, reading and verifying memories) plus a pointer to a new bundled `auto-memory` skill that carries the full type guidance with examples, the metadata rules, team memory and the persistence section. The route reuses resolveBundledReferenceRoute: pointer, pointer via tool_search, the full policy where no skill can be loaded, and the resident policy alone when the user disabled the skill. The policy is rebuilt once the tool registry exists, since the session-start memory refresh runs before it. The skill is listed only with managed auto-memory on. The extraction, Dream, /remember and import agents keep the full constants; buildManagedAutoMemoryPrompt output is unchanged. A two-way anchor test pins every constant line into SKILL.md and keeps the moved text out of the resident policy. Default sessions send 4,572 instead of 15,868 characters for this section (about 2.8k fewer tokens per request). The bundled listing grows by 233 characters; its budget moves from 6,800 to 7,050. Refs #16291 * fix(memory): resolve the auto-memory route per reader and keep the daemon catalog working The daemon's skills catalog builds its SkillManager on a Config shim without isSkillEnabled. The bundled auto-memory skill is in core's bundled directory, so the conditional-skill check called it, threw, and returned an uninitialized catalog. The shim now answers it from the workspace's managed auto-memory setting. The main-session policy was built once on the session Config, so every derived Config inherited its pointer: a subagent without the Skill tool (no skill manager, #12424) was told to load a skill it cannot reach. The policy now keeps its inputs and resolves the route in getAutoMemoryPrompt for the Config that reads it, built once per route. That also replaces the post-registry rebuild and follows Skill tool reachability between refreshes. - List auto-memory only under legacy recall; by-name lookup uses the same conditional set as the listing. - Put back two index rules the resident policy had lost: MEMORY.md has no frontmatter, and lines past the index limit are truncated. - Remove MemoryManager.buildAutoMemoryPrompt, which had no callers. - Fix the getAutoMemoryPrompt docblock, read SKILL.md once in its test, and update the design doc and measured lengths. Refs #16291 * fix(memory): load the auto-memory reference without an approval prompt Private memory writes are auto-allowed, but the Skill tool asks by default, so in the default approval mode every memory save began with a confirmation to load the reference. The Skill tool now allows the call when the name resolves to the bundled `auto-memory` (read-only text, no allowedTools, no hooks); a same-named project, user or extension skill still asks. Also share the memory tier lines between the full and resident policies, and name the skill through one constant in `skills/types.ts`, used by the skill manager, the Skill tool and the daemon's skills catalog. |
||
|
|
07a4fda666 |
feat(cli): run the Ink TUI's tool batches on the shared loop's batch (#16304)
* feat(cli): run the Ink TUI's tool batches on the shared loop's batch With experimental.unifiedAgentLoop on, the tool calls of each Ink TUI model round run as one batch of the shared loop, runToolBatch, on an executor over the TUI's own scheduler. The tool group, approvals, Esc and the Mod tool middleware are unchanged; the batch's completion goes back to the executor, and the TUI's completion step continues the turn with the batch's results. - useReactToolScheduler's schedule takes an optional onBatchComplete that receives that batch's completion in place of onComplete. - createInkToolExecutor implements AgentLoopToolExecutor over it and marks the calls the Mod tool middleware already counted, through a new optional `recorded` on the loop's settled call. - runToolBatch also returns its finalized parts by call, `responses`, so the completion step can leave out results it does not send. - The scheduler's completion handling moved into commitCompletedToolBatch, which both paths call; handleCompletedTools skips the steps the shared batch did. A response made only of provider replays keeps the current path. - The Ink parity suite counts shared tool batches through the debug log and adds a replayed call next to a fresh one, an approved then declined write, and a shell command cancelled with Esc. - The setting's description and the design doc (3b split into 3b-1 and 3b-2, with 3b-1's intended difference) are updated in both languages. * fix(cli): send and record only the shared tool batch results the TUI keeps - runToolBatch takes `deferRecording` and returns each result's recording, so the Ink TUI records only what it sends, after its session check; a result that replaces a placeholder is recorded once, and dropped or secondary-owner results are not recorded. - A shared batch that completes during a model stream is queued like the current path's batches and handled when the streams end, against history as it is then, in one continuation with the other waiting batches. - Calls the shared batch ran are not counted again by the history-dedup and secondary-owner steps. - The batch's replay answers keep the continuation going when every executed call was cancelled. - A failed shared batch falls back to the current completion path for its settled calls; a schedule that ends without completing releases the batch's callback. - Provider call fingerprints are recorded when the batch is scheduled. * fix(cli): keep a shared tool batch in the session and render it completed in - Take the session and chat when the scheduler completes the batch, before it is finalized, so a /clear or /resume while a large result is persisted no longer sends or records that result in the new session. - Commit the tool group inside the scheduler's completion callback, in the render that clears its display list, instead of after the finalize. - Leave cancelled calls to the TUI to count, so a cancelled call whose id history already answered is not counted. - Merge queued shared batches' results one per call id. - When the batch throws, drop the replay ids it marked as answered but never sent. * fix(cli): continue a shared tool batch with the TUI's current state and settle it when its completion throws - runSharedToolBatch commits through commitCompletedToolBatchRef and reads the chat from the client the TUI holds now, so a batch that waited on approvals or long tools continues with the current handleCompletedTools, submitQuery, quota fallback and client, as the scheduler's onComplete ref does. - The Ink executor resolves the batch in a finally block, and the session capture in onSettled no longer throws: a chat that cannot be read (a reset under way) ends the batch without a continuation and releases the turn's activity instead of leaving it Responding. |
||
|
|
cf6b4270c6 |
perf(managed-agent): take ordinary publication lanes off the placement guard (#16354)
* docs(managed-agent): design publication lanes without the placement guard Placement owners lock the original binding and the Session root in both orders (lockBindingControl: binding then root; lifecycle verification and hosted cleanup: root then binding), so a lane cannot replace placement with a binding lock in one fixed position. Ordinary lanes instead lock the Session root first, read a session-class binding without a lock (its transitions out of READY hold that root) and share-lock a workspace-class binding. Co-Authored-By: Claude Opus 5.5 <[email protected]> * perf(managed-agent): take ordinary publication lanes off the placement guard An ordinary (non-CSI, non-Resident) publication lane now locks its Session root where it took tenant placement. It reads a session-class original binding without a lock, on the repository's own connection: every move of such a binding out of READY holds that root. It share-locks a workspace-class binding, whose writers take no root. Placement owners lock the binding and the root in both orders, so neither fixed binding position would be safe without placement. Placement stays for CSI and Resident Workspace originals, a session-class binding of another Session, and lifecycle-authority receipts. The writer takes placement only when the lane took it first. Receipts refuse hosted cleanup records, whose commit would take placement after the root. Operator recovery locks a session-class binding's Session root before its storage rows and the binding. --------- |
||
|
|
83248491cf |
perf(managed-agent): take queue dispatch and workspace session creation off the tenant lock (#16327)
startNext locked the tenant placement guard before its Session row, a leftover from when Turn admission took the guard; the Session row now comes first, as in Turn admission. Workspace Session creation now locks the selected Workspace row as its first statement instead of the tenant guard, so creations in different Workspaces of a tenant run at once. Selections that may resolve to a configured Resident Workspace keep the guard, because its single-Session range check shares gaps across Workspaces. The default Workspace is resolved through its generated unique key, so a locking resolution takes one row instead of every Workspace row of the tenant. Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
4b980c9636 |
perf(core): defer workflow tool schemas by default (#16310)
* perf(core): defer workflow tool schemas by default * test(core): preserve visible workflow in mod permission fixture * test(integration): expect deferred workflow in headless requests |
||
|
|
ef9c891b8f |
fix(ci): clear the pinned corepack cache before every install (#16351)
* fix(ci): clear the pinned corepack cache before every install The Linux test shards have been dying seconds into Install dependencies with MODULE_NOT_FOUND on the cached pnpm's bin/pnpm.mjs since #16328 pinned COREPACK_HOME to runner.temp (main runs 41876940141, 41889761157, 41927887452; six different self-hosted hosts). The pin assumed runner.temp is per-run, but self-hosted runners keep _work/_temp across jobs — the lint job's isolated-HOME step already documents this. corepack trusts an existing cache entry without re-verifying its files, so a partial pnpm tree left by an earlier job fails every later job on that host. Clear the pinned cache at the start of the shared install body in all three jobs that copy it, enforcing the per-run contract the pin documents. The guard keeps the reset a no-op where COREPACK_HOME is unset (test_build) or already run-unique (lint). The test step still reuses the cache install warms in the same job, and poisoned hosts self-heal on their next job without manual cleanup. Pin the reset and the install/test COREPACK_HOME match in the workflow meta-test, and bump the size baseline for the added lines. * fix(ci): scope the corepack cache reset to the shard-owned path Review found two over-deletion shapes in the nonempty guard: a runner that exports COREPACK_HOME would lose its entire shared cache in the build job, and hosted lint would lose the cache its pnpm-store step had just warmed, re-exposing the install to a registry outage. Match only the exact shard-owned path instead; the build and lint copies become unconditional no-ops and the three install bodies stay byte-identical. Add an execution test that runs the shipped guard fragment under a scrubbed environment across five cache shapes: it kills the dead-guard mutant the string pins survived, and a widened-guard mutant dies on the shared-cache survival cell. State the incident as observations with the corruption source undetermined, and correct the stale per-run rationale beside the pin. |
||
|
|
f3488b47fa |
perf(managed-agent): take ordinary tool publication and read leases off the tenant lock (#16335)
* docs(managed-agent): design publication lanes without tenant-wide guards Publication lanes take the placement and output_publication tenant rows on every call. The design separates their three roles: tenant byte quota on a dedicated guard domain for reserve only, a per-Session lock order shared by lanes and collection, and a shared lock on the original Runtime binding in place of placement. Co-Authored-By: Claude Opus 5.5 <[email protected]> * perf(managed-agent): take publication reserve and renew off the tenant lock The ordinary apply lane (reserve, renew, fence, no-start release) took the tenant output_publication guard for every call, mostly so that a reservation's tenant byte and active-capture sums were exact. Those limits are protective caps, so a bounded, self-correcting overshoot by concurrent reservations of different Sessions is accepted. The lane runs at READ COMMITTED and its Session root keeps the Session limit exact. The lane already locks the Session root and head before the publication row, as collection does. Its evidence reads took a read lease through the tenant guard after the root, which inverts retention's order; they now take the lease under the Session locks the writer already holds. CSI publications keep the guard. Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(managed-agent): correct the publication lane order in the guard design Ordinary publication lanes already lock the Session root and head before publication rows, as collection does; only CSI lanes lock the publication row first. C2 therefore needs the post-root guard audit, not a reorder. Co-Authored-By: Claude Opus 5.5 <[email protected]> * perf(managed-agent): take ordinary publication lanes and read leases off the tenant lock Ordinary producer, admission and receipt lanes and the tool-result projection worker no longer take the output_publication guard. Below the guard they already lock the Session root and head before publication rows, as collection does, so no lane changes its order. CSI originals keep the guard, as does a receipt whose commit carries lifecycle authority. Read leases take only the Session root and head: every collector that checks them holds the same root and head, and retirement updates the root. This replaces the Session-locked read variant added for the apply lane and removes the guard from Artifact and resource reads. The convoy test now runs every ordinary lane while the guard is held, so a lane that takes it after its root stalls there. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(managed-agent): avoid tenant guard during read lease renewal Keep renewal serialized by the Session root and preserve retirement generation and expiry checks. Add a MySQL regression that forces renewal while another connection holds the tenant guard, and synchronize the bilingual design. Co-authored-by: Qwen-Coder <[email protected]> * fix(managed-agent): bound publication cleanup and preserve lock order Co-authored-by: Qwen-Coder <[email protected]> * fix(managed-agent): keep projection completion off tenant guard Reuse the root and head held by projection admission while retaining private Session mutation refusal. Add a full-completion contention regression and align the lock registry, dead helper pin and bilingual design. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
5f997b69d7 |
fix(managed-agent): bound runtime concurrency and improve error handling (#16318)
* fix(managed-agent): add guideline reports and bound runtime concurrency Co-authored-by: Qwen-Coder <[email protected]> * chore(managed-agent): remove PMD integration Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
8a7c0d17d8 |
fix(managed-agent): find the Flyway scripts in the module jar the server ships them in (#16175)
* fix(managed-agent): find the Flyway scripts in the module jar the server ships them in Since #15871 split the server into Maven modules, its Flyway scripts ship in BOOT-INF/lib/qwen-managed-agent-persistence-*.jar rather than in BOOT-INF/classes/db/. The upgrade command's check read only the classes, so on every archive since then its database row warned "cannot compare the Flyway scripts (no Flyway script found in ...)", and a release that changed or reordered a script the installed database ran went unflagged. migrationScripts() now also reads db/**/ at the root of each nested jar in BOOT-INF/lib/. Spring Boot stores those uncompressed, so their directories are read in place; a compressed one, which Spring Boot would not load, is skipped. A script keeps its path as its name wherever it ships, so one moved from the classes into a module compares as unchanged. The reference distribution workflow now reads the scripts out of the exact archive it built, so a later change of layout fails there rather than turning check's row into a standing WARN. * fix(managed-agent): read a Flyway script shipped twice with different contents as unreadable The reader keys the scripts on their classpath path, and the nested jars give that key a second source. Two copies of one path with different contents left whichever the central directory listed last in the map, so the database row compared one arbitrary copy, while the class loader would hand Flyway the other. Matching copies are still one script; differing ones now make the jar unreadable, which the row reports as "cannot compare the Flyway scripts". * fix(managed-agent): name both places that ship a differing Flyway script, and pin the local extra field - When one script path ships twice with different contents, the error now names the two places, BOOT-INF/classes or the nested jar, so the operator need not unpack the jar to find the second copy. - The test zips can carry a local-header extra field, as archivers add for timestamps or alignment, and the nested-jar case reads entries past one, so dropping that term from dataStart fails the suite. |
||
|
|
a18ecb26c8 |
docs: configure Qwen3.x served under a custom model name (#16342)
* docs: configure Qwen3.x served under a custom model name The local self-hosted section never pointed at the reasoning override, so a Qwen3.x model served as e.g. --served-model-name chat kept thinking on in side requests and used the 200K default window. Add a short subsection with a model entry that declares the qwen-chat-template profile and the server's context window, and the server flags tool calling needs. Refs #16340 Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs: move the custom model name subsection to the end of the local section Keep the API key and note blocks under Local Self-Hosted Models, as suggested in review. Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
b9e20830f7 |
fix(verify-pr): post flaky demotions on closed PRs and pin the blank class (#16135)
* fix(verify-pr): post flaky demotions on closed PRs and pin the blank class The closed-PR skip from #16050 also caught the flaky variants of the cancelled, failed-before-report and results-unavailable notices, and the report body of a round that wrote no report.md. Those render ❌ and, on a merged PR, name non-deterministic tests now on main, so they post unless this run's own report already carries the demotion. - publisher: the closed-PR skip leaves FLAKE_VERDICT=flaky alone, except beside this run's own report - publisher: the gates line says its tests are "not counted individually above", since each suite run now counts as one assertion (zh: 不逐条计入上面的断言) - checker tests: pin the blank class on their own (whitespace refused, U+0085 and U+FEFF accepted); before, only the shared parity table in the workflow suite caught a revert to trim() - SKILL: rewrap the over-long local-path line * fix(verify-pr): scope re-run advice to open PRs and pin the gate condition The results-unavailable notice can now land on a merged PR, where the verify lane refuses `@qwen-code /verify`: both variants now offer the re-run only while the PR is open. The flaky exemption keys on the gate verdict, which holds only because the gate step runs on a prepared round; a test now pins that condition. * fix(verify-pr): skip a flaky notice only beside a report that carries it On a closed PR, a flaky notice with no live status of its own to finalize is skipped beside this run's own report, as a duplicate of the demotion that report carries. That report can come from an earlier attempt whose gate did not diverge. A re-run attempt whose own live status is missing (that post is best-effort) then lost its ❌ to the run log, beside a report that never showed it. A flaky verdict now adds a verify-flaky marker to the header, and the closed-PR skip drops a flaky notice only when this run's own report carries that marker in its header. * test(verify-pr): pin the own-comment lookup's initializers The publish step runs under `set -u`, and a failed comment listing or bot identity lookup leaves OWN_ID, OWN_SUBSTANTIVE and OWN_FLAKY at their initial values. Dropping any of those three lines kept the suite green: no case made either lookup fail. On such a run the step would instead abort with "unbound variable" and post nothing. The upsert stub can now fail the listing or the identity lookup. Under either failure, a report posts fresh rather than patching a status it cannot see. A flaky notice on a closed PR also posts, because the lookup cannot tell whether this run's report already carries the demotion. Deleting any one initializer now fails the test on its own unbound variable, which the harness now prints with the failed exit status. |
||
|
|
2800ea18da |
fix(cli): keep project hook approval env inside the workspace (#16316)
* fix(cli): keep project hook approval env inside the workspace
Approved project hooks were silently withheld in the docker sandbox lane.
The workspace trust walk admits an ancestor `.env`, the host hashed it into
`hookApprovals.json`, and the container — which mounts only the workspace —
recomputed the hash without it, so `getState()` returned `pending`.
Narrow `readProjectEnvFiles` to files under the workspace root. `findEnvFiles`
still discovers ancestors for the runtime environment, so only the approval
hash input changes. Hooks in a workspace with no ancestor `.env` keep their
current record, but every hash that covered an ancestor changes once, so those
approvals reset to pending a single time.
Fixes #16313
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-issue-patrol/jmw24pta9wt
* style(cli): satisfy prettier in environment hook-approval test
The incremental Prettier lane selected packages/cli/src/config/environment.test.ts
and rejected the single-line hashProjectHooks call added in
|
||
|
|
9123d5ee04 |
fix(managed-agent): refuse every URL fetch refuses and say why a health probe failed (#16110)
* fix(managed-agent): refuse every URL fetch refuses and say why a health probe failed start and verify refused a listener URL only when URL.canParse rejected it, so a --server-url without a scheme (proxy.test:8443), with a user name or password, or on a port the Fetch standard blocks (6000, 10080) passed the check and then failed every probe until --timeout. They now refuse each of those before anything runs, and a test checks that the runtime's fetch rejects every URL the check refuses. A probe that is sent and still fails now says what it got: start's timeout names the last probe's answer and verify's health row names its own, such as "HTTP 503, status DOWN", "self-signed certificate" or ECONNREFUSED, where both said only "not UP". The listeners() comment names the reachesHarness option verify passes. * fix(managed-agent): refuse a Harness URL that is not http or https in upgrade start start checked the Harness URL with new URL() alone, so an opaque value such as proxy.test:8443 parsed with no host and no port: start then waited out --timeout for the Harness to listen on port 80 of an empty host. It now refuses any Harness URL that is not http or https before it starts a unit, as the preflight would fail it later anyway. The probe-answer test also runs the runtime's own fetch against a port nothing listens on, so the ECONNREFUSED wording comes from a real error rather than a constructed one. * fix(managed-agent): keep the endpoint's answer when the deadline cuts start's last probe short start's last health probe gets the 1 ms the deadline leaves it, so against a real endpoint it times out before any answer arrives, and the timeout message reported that abort instead of what the endpoint had been answering, such as HTTP 503, status DOWN. A probe that times out on a budget the deadline cut below the per-probe cap no longer replaces an earlier answer; one that runs out of its own 5 seconds still does, since the endpoint stopped answering. The test rig's fake fetch now awaits a response function, so a test can model an endpoint that answers late and honours the abort signal. Also pins the IPv6-zone diagnosis to a percent inside the brackets. * fix(managed-agent): treat a health body the deadline stalls as a cut-short probe too A health endpoint that sends its headers and then stalls the body made response.json() reject with the signal's TimeoutError. That branch did not report timedOut, so start's guard took the probe for an answer, and a probe the deadline cut short replaced an earlier HTTP 503, status DOWN with "HTTP 200, The operation was aborted due to timeout". The body-read branch now reports timedOut as well. The last-answer test gains two real-socket cases: a body that stalls after its headers keeps the earlier answer, and a refused connection after an answer replaces it, which pins the timedOut half of the guard. The design doc says when the 5-second limit counts as the probe's own, and that any other failure replaces an earlier answer. * fix(managed-agent): sanitize health text and align upgrade refusals Address review R1-1..R1-5 on the upgrade URL/health change: - R1-1: neutralize terminal control bytes (C0/DEL/C1 -> U+FFFD) in the endpoint-derived health answer before it is interpolated into start's timeout refusal and verify's health row, so a queryable endpoint cannot move the cursor, split a row, or write the clipboard (OSC-52). - R1-2: the Harness URL refusal reuses fetchRefusal when the value does not parse, naming an IPv6 zone exactly as --server-url does. - R1-3: start's timeout labels the retained value 'the last answer', matching the design doc, since a deadline-cut probe keeps an earlier answer. - R1-4: pin verify's URL refusal (listeners runs it with reachesHarness:false) with a test asserting it refuses before any probe. - R1-5: tear down the 'closes' test server via t.after so a failing assertion cannot hang node --test to the job ceiling. Tests: 63 pass; R1-1/R1-2/R1-4 mutation-witnessed (printable no-op, refusal gated on reachesHarness, zone reason reverted each redden their pin). Prettier and ESLint clean. Co-authored-by: Qwen-Coder <[email protected]> * test(managed-agent): exercise the health sanitization over a real socket Add a real-scenario pin for R1-1: a local HTTP server serves a status field carrying cursor moves, a newline and an OSC-52 clipboard write, and verify runs against it through the runtime's own fetch — the real health() -> printable() -> row() -> out() path, not the rig's fake. Asserts the printed health row keeps no control byte while the readable text survives. 64 tests pass. Co-authored-by: Qwen-Coder <[email protected]> * fix(managed-agent): mask a refused URL's password and name why verify's API reads failed Found on a real systemd deployment (real control plane, Harness, MySQL, TLS and nginx): - A refused --server-url was printed as typed, so a URL with a password, which fetch refuses, put that password in the output. The refusal now prints it as ***. - verify's session and turn rows still said only "fetch failed" where the health row above them named the cause, such as "self-signed certificate in certificate chain". API reads now take the same cause. - Text the API hands back (an error reason, a body that fails to parse, a Session ID, an event type) reached the session and turn rows with its control bytes; it is now neutralized as the health answer is. The design doc says the probe answer's control characters show as U+FFFD. * fix(managed-agent): show a health status that is no string as its JSON printable() ran String() on what the endpoint returned, and String() throws on a valid JSON object whose own toString is no function, such as {"status":{"toString":1}}. health() is not inside a try, so on a real deployment verify exited with "Cannot convert object to primitive value" before it printed its health row, and start's probe loop stopped at the first probe. A value that is no string is now shown as its JSON, which is also what the endpoint actually said; an API error reason gets the same. * fix(managed-agent): make the upgrade reports total and bounded, and refuse only what a phase dials Review round 5 traced its findings to two shared causes, fixed at the source rather than one site at a time. Endpoint text in the reports (printable, withoutCredentials): - printable() no longer throws on any parsed JSON value: a status nested deeper than JSON.stringify() recurses showed a bare "Maximum call stack size exceeded" from verify and left start's server running without its guidance; it is now shown as [unprintable]. - It also stops at 200 code points, cut after the control bytes are gone and never inside a surrogate pair, so a long status cannot scroll the report away. The item count on the session row goes through it too. - withoutCredentials() masks the whole user information, so a token in the user-name position is masked, and masks it textually when the URL does not parse. The Harness refusal masks its URL as well. URL checks (listeners, the Harness guard): - A phase is refused only over a URL it dials: start and verify probe the health endpoint, and only verify --tenant reads the API. An API on a port fetch refuses behind a separate management port no longer stops start. - The Harness URL is checked as the preflight checks it, an http(s) origin without user information, a query or a fragment, so start refuses it before it restarts a running Harness rather than after. * fix(managed-agent): take the Harness verdict from the preflight and check the API URL only when verify reads it - verify decides whether its session row reads the API before it checks the listeners, so a signed deployment, or --actor without a trusted actor header, is no longer refused over an API URL it never dials. - The configuration resolver now prints the preflight's own verdict on the Harness URL, read untrimmed as java.net.URI reads it, and start refuses on it before restarting the Harness: a host with an underscore, an empty user name or a padded value no longer restart the Harness only to fail the preflight. - withoutCredentials masks everything up to the last @ of a value the parser rejects, so a password holding / ? or # stays hidden. - printable counts code points in one pass and keeps only the head, so a huge endpoint answer costs no copy of itself. - The request an API error names is bounded like the rest of the row. * fix(managed-agent): print the Harness URL untrimmed, as the server parses it A padded qwen.managed-agent.harness.base-url fails the preflight, and the start refusal now shows the padding instead of a value that looks valid. * fix(managed-agent): judge the Harness URL only where the server reads it The resolver passed on the preflight's verdict on the Harness base URL with no regard to the Harness's state, so start refused a value that a disabled or pooled Harness leaves unread: the preflight skips its harness row before reading it, and the server never dials it. The verdict is now "skip" there. start refuses on the preflight's grounds (user information, a query, a fragment, or anything java.net.URI reads differently) only on a "false" verdict, and its own reasons only word that verdict. It still refuses a URL that is not http or https, since it waits for the Harness there. Without a verdict, as when the configuration does not resolve, the preflight judges the URL once the Harness is up. verify now checks the type of the values the API chose before using them. Building a route from a Session id, matching an event type, or reading an event sequence coerced the value first, and that throws on an object such as {"toString":1} or on an array nested deeper than the stack, so printable() never got to show what came back. A Session without a string id fails its row with that value, an event type that is not a string fails the turn row, and a sequence counts only as a number or a numeric string. The start tests now check every run's output for the password, not only the last run's. * fix(managed-agent): judge a pooled Harness URL, and read only lists from the API The previous commit gave "skip" for a pooled Harness as well as a disabled one, since the preflight skips its harness row in both before reading the base URL. A pooled server still reads that URL, though: each member's QwenHostedHarnessConnector is built from it and refuses it by the same rule as the preflight. The pool's refresh drops a member whose connector refuses it, so such a pool accepts no Session while the server reports UP. Only a disabled Harness gets "skip" now. A test pins the verdict to whether the connector builds, for a single Harness and for a pool. Since the verdict is now the server's rather than only the preflight's, the refusal says "the server does not take". verify reads Sessions, items and events only as lists. Data that is not a list fails its row and says what came back: iterating it would throw, or walk a string, and its length could be any value. A listed entry of 0, "", false or null fails the session row instead of being reported as no Session. An event sequence moves the cursor only as a whole number, and only forward: Number() reads "Infinity", "0x10" or " 4 " as numbers. The user guide now says that, when the configuration does not resolve, the preflight judges the Harness URL after the Harness starts. * fix(managed-agent): keep start's own Harness URL rules without a verdict When the configuration does not resolve there is no verdict. The previous commits then let start accept any http(s) Harness URL and left the URL to the preflight. A pooled preflight never reads it, though, and every pool member's connector refuses user information, a query or a fragment, so the pool would start and accept no Session. Without a verdict, start refuses those itself again, since the server refuses them wherever it reads the value. A "true" or "skip" verdict still asks nothing more, and the generic wording is kept for a "false" verdict. HarnessConfigurationTest now builds a pool through the real member factory and shows that the single base URL decides whether a member is accepted. The pooled half of the agreement test checked nothing the single half did not, so it is gone. verify now requires every page to carry its data as a list: a body that is no object, or one whose data is null or missing, fails the row instead of reading as no Session. A Session id of "", "." or ".." fails, since fetch resolves dot segments and an empty id reads the list. Only terminal: true ends a turn. The docs no longer say the server takes only an origin, since a path is fine. They say which component refuses the URL, and they word the sequence rule as the code applies it. * fix(managed-agent): sort the test's static imports, and refuse a --server-url query The Mockito static import sat between two JUnit ones, so the verification module's ImportOrder check failed. The static block is now sorted. start and verify append /actuator/health and the API paths to --server-url, so a query or a fragment there took the paths in. With http://proxy.test/?a the command probed /?a/actuator/health, and fetch dropped a fragment along with the path after it. Such a URL is now refused up front. A listed Session entry with no id is now shown as itself rather than as "undefined". A health answer whose JSON parse error quotes control bytes is pinned to come out sanitized. --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
4d4e4fc9ca |
feat(channels): role-aware action templates for GitHub bound-session delivery (#16258)
* feat(channels): role-aware action templates for GitHub bound-session delivery Bound-session delivery injected a hardcoded generic prompt and routed each event to a single winner, so a PR followed by review, verify, and fix sessions in different workspaces could only ever reach one of them, with no declarative way to say what each should do. Bindings gain an optional closed-vocabulary role (review/verify/fix): the command-position backfill mints review/verify from /review and /verify-pr, fix binds explicitly through the metadata route (null clears), and a re-offer never overwrites an existing role. A new channel-level boundSessionActions record maps event classes (watch:<type> or notification reason, optionally role-qualified) to operator instruction templates rendered with GitLab-style %var% substitution into a labeled trusted block; substituted GitHub values stay sanitized, capped data. With the map configured, routing generalizes the base design's single-winner decision to one winner per role with fan-out across roles: per-(event, target) durable tasks, independent backpressure, role-scoped gone fallback, and public fallback only at zero live followers. With the map absent, every behavior and the generic prompt stay byte-identical (pinned by tests). Design: docs/design/2026-11-04-github-channel-bound-session-action-templates.md (EN/ZH); the base design's Key decision 4 carries a forward pointer. Co-authored-by: Qwen-Coder <[email protected]> * test(cli): expect the command-stamped role in backfill binding assertions The /review command now stamps role: 'review' on every binding form it mints (role-aware bound delivery). Four strict-equality assertions in the backfill suite predated the field: the Aone URL-form and unplanned-repair cases, the fork-url state-pairing case, and the capped-plan live-bridge sync projection. CI's sharded Test lane surfaced only the first before fail-fast skipped the remaining shards; the full local suites (backfill 177, server 1442, bridge 1043, transport 431, daemon-worker 182, session-list/pr-refresh 87) now pass. Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): delete a record key when its editor row is cleared Addresses the triage review's record-renderer observation. The save serializer already filtered empty values (so no unsavable state existed), but the form state kept a touched-then-cleared key as "" until save; the editor now deletes the key on clear, matching what the upsert payload will contain. A new dialog-level test pins the end-to-end outcome for the 80-key boundSessionActions surface (and the GitLab record fields sharing the renderer). Also documents at roleByNumber that per-number uniqueness is guaranteed upstream by noteMention, so the Map constructor's last-wins semantics cannot override the first-wins command rule. Co-authored-by: Qwen-Coder <[email protected]> * fix(channels): close the role-aware delivery review findings (C1-C5, D1-D3) C1: the first task of a multi-target comment dispatch now carries the full fan-out intent; recovery reconstructs missing siblings BEFORE applying the survivor's event-wide dedupe, so a crash between claims no longer loses the unclaimed roles permanently. C2: a durable bounded delivery ring (github-bound-delivered.json) records every private acceptance; no gone-fallback or untargeted recovery may route an already-delivered event publicly, even after every follower disappears and the daemon restarts. An unreadable ring fails closed for the public fallback. C3: delivery-time re-validation retires a role-aware comment whose role lost its match on a complete snapshot instead of injecting the obsolete role's instruction into the frozen session. C4: split-sidecar merge lets the newer half win on role, absence included, so an explicit role:null clear survives an archive/restore merge. C5: FIELD_LABEL_KEYS.github registers boundSessionActions, making the zh-CN label and description reachable. D1: the map-skip retire now requires a complete fresh snapshot; under a partial or failing scan the event defers as backpressure instead of being consumed on incomplete evidence. D2: comment-lane events resolve a lane class (comment) after the thread reason, so fix:comment catches comments delivered under mention/subscribed/author while reason-specific keys still win. D3: the worker accessor validates roles against the shared PR_BINDING_ROLES constant instead of hardcoded literals. Each correctness fix has a dedicated regression; C1/C2/C3 regressions were mutation-witnessed (removing the fix turns them red). Design docs (EN/ZH) and the user guide updated to the new semantics. Co-authored-by: Qwen-Coder <[email protected]> * fix(channels): close the role-aware delivery re-review findings (C1 residual, R2-1..R2-4) Co-authored-by: Qwen-Coder <[email protected]> * fix(channels): complete fanout slots durably and keep private retirement terminal (R2-2/R2-4 residuals) Co-authored-by: Qwen-Coder <[email protected]> * fix(channels): make the fanout completion mark fail-closed with durable acceptance evidence (R2-2 write-failure gap) Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
b2463bf606 |
perf(managed-agent): share the storage fence in session command admissions (#16329)
MCP and extension management checks, model and approval changes, context compression, task cancel and action responses checked the storage fence with exclusive locks, so every Session on one storage queued on its alias and directory rows. None of them writes those rows later in its transaction, so they now use the read-only admission form (FOR SHARE on MySQL, the exclusive fallback on H2). Fence writers still wait for every admitted command. The publication writer stays exclusive because its receipt lane may commit with lifecycle authority in the same transaction. Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
b9f4a9ee43 |
fix(managed-agent): bound runtime thread pools (#16330)
Bound HTTP dispatch and local runtime operations, readers, and stop escalation. Preserve retryable admission refusal and durable recovery identity under saturation. Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
2b9c4a98f4 |
fix(ci): pin COREPACK_HOME for Linux test shards on cold-HOME runs (#16328)
The Linux unit-test shards set HOME to a fresh per-run directory for isolation. scripts/tests/corepack-warmup.js (vitest globalSetup) spawns `corepack pnpm --version` before any worker forks, and on a cold HOME corepack downloads the pinned pnpm from registry.npmjs.org. The ECS egress intermittently times out on that fetch (ETIMEDOUT to 104.16.9.34), which kills the shard after every test file has already passed — today that produced 7 PR-facing failures across random shard numbers (2/8, 5/8, 3/8, etc.), all on the same self-hosted host. Install dependencies runs before the test step and warms the corepack cache under the runner's real HOME, but the test step's HOME override makes that cache invisible. Pin COREPACK_HOME to a per-run path in both steps so they share the same cache without touching the HOME isolation. Per-run, not persistent: corepack does not re-verify extracted entries on cache hit, so a shared cache would be a poisoning channel (the same reason lint_and_static keeps its COREPACK_HOME per-run). Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
8e9b429ff8 |
perf(managed-agent): index the Runtime slot lookup by isolation key (#16325)
The CSI marker probe runs on every ordinary Session write, including each activation renewal, and looks up the Session's Runtime slots by tenant and isolation key. No index covered that lookup, so every call scanned the whole slot table, and the fence's locking variant locked every Session's slots. Index ma_runtime_slot.isolation_key, as ma_runtime_binding already does. A MySQL integration test pins the probe's plans at 1,000 and 10,000 slots and checks that the fence leaves other Sessions' slots writable while still holding off a second slot for the fenced Session. |
||
|
|
624d10532a |
perf(managed-agent): avoid storage locks in read preflight (#16312)
* perf(managed-agent): avoid storage locks in read preflight * codex: fix CI failure on PR #16312 |
||
|
|
f217a8e5d9 |
fix(managed-agent): restore JournalCommit under its class-size ratchet (#16323)
#16284 pinned JournalCommit at 880 lines and #16275 grew it to 886, so every Java lane fails ClassSizeRatchetTest on main. The hosted-cleanup admission probe that #16275 added belongs with the cleanup bookkeeping it guards, so move it behind HostedCleanupReferences.containsCleanup and restore the class to its pin. Fixes #16321 |
||
|
|
d4083f4c82 |
fix(serve): read PR sidecars directly for the bindings snapshot (#16241)
* fix(serve): read PR sidecars directly for the bindings snapshot GET /sessions/pr-bindings paginated the activity-ordered session list and had to flag every multi-page scan partial, so a workspace with more than one page of active sessions never produced a complete snapshot and the GitHub channel deferred every unmatched PR event to the dead-letter horizon instead of answering through the channel. List the workspace's *.pr.json sidecars once (the enumeration the PR refresh timer already uses), read each sidecar, and take attribution and activity from the session's persisted record, refined by the live session when it is live. partial now means an I/O failure only. Scan draining and transitioning workspaces too: their files stay readable and they still serve sessions. Exclude machine-owned agent task sessions explicitly. Update both design docs, the user guide and the adapter's partial-snapshot diagnostics accordingly. * test(serve): compare aggregated PR bindings as a set Directory listing order and the random temp directory names made the cross-workspace assertion order-dependent; CI sorted the uppercase suffix first. Compare the (workspace, session) pairs as a set. * fix(serve): harden the PR bindings sidecar scan Append bindings the bridge holds in memory but that have not reached a sidecar yet, so the window between the bridge update and the sidecar write (or a failed write) cannot read as an authoritative absence. Treat a transcript that exists but yields no records as unreadable (partial) instead of listing the session unattributed, skip an active sidecar left behind by an archived session, decide non-targets before counting failures so a machine-owned session's corrupt sidecar cannot make the answer partial, emit tombstone rows without the transcript projection, validate live timestamps before the recency comparison, and log per-session failures as one line per workspace scan. Remove the write-only prsReadFailed flag now that the snapshot reads sidecars itself, retire the last draining-workspace wording from the adapter's diagnostics, and state the real per-row cost and the reason for having no page cap in the design doc. * fix(serve): treat a failed live session list as a partial bindings answer The live list is the only source for a binding that has not reached a sidecar, so a failed read must make the answer partial instead of reporting it complete without those bindings. Merge bridge-held PR numbers into a listed non-empty row (a tombstone stays empty), decide hidden-catalog and archived ownership for empty sidecars too, compare activity timestamps with the shared chronological rule, and log a registry entry without a runtime like the other partial causes. Reword the adapter's partial diagnostics to name every cause and count listed rows, update the shared snapshot contract in channel-base, pin the corrupt-sidecar row's presence in the session-list test, and make the design docs' cost model consistent. * fix(serve): decide live-only binding ownership from the persisted record A restored agent task session carries no sourceType on its bridge entry, so the live-only branch read ownership from a field that is not there and emitted a machine-owned session as a delivery target in a complete answer. Read the persisted record first, as the sidecar loop does, and drop the isArchived guard the bridge never populates. Cap the bridge merge the way the session list does (append only while the persisted list is below SESSION_PR_LIST_LIMIT, then keep the newest) and log the no-runtime cause under test. Keep the adapter's partial hint firing while retention holds bindings an empty partial no longer lists, and stop blaming the binding lookup when the cold-empty cap was reached by successful empty partials. Name the live-list cause in the design docs' partial definition and state the two cost legs separately. * fix(serve): keep unknown sessions unlisted when merging bridge bindings A session whose sidecar or transcript could not be read is counted as unknown and left out of the answer, but the bridge merge re-listed it from its live PR subset, which made the adapter treat it as enumerated and drop the row it was retaining with the persisted numbers. Track the unknown sessions and skip them in the merge. Attribute live-only rows through the same resolver as sidecar rows so an unreadable transcript makes them unknown too, count live-only sessions in the diagnostic's denominator, and pin that transitioning and blocked workspaces with a runtime are scanned. Make the adapter's partial hint say the lane is deferred while the cold-empty gate holds instead of claiming issue events flow, and name the binding lookup when failed lookups seeded the cold-empty cap and a single empty partial lands it. State the live-merge rule and the real filesystem-leg ceiling (sessions ever bound) in both design docs. * fix(serve): decide binding ownership before any read that can fail A machine-owned session whose transcript or sidecar could not be read was counted as a scan failure before its ownership was consulted, which made every answer partial on behalf of a session that is never a delivery target. Resolve ownership from whichever record states it as soon as the id is known, and skip non-targets before the sidecar read. Key the bridge top-up by PR number and repository so same-numbered PRs from different repositories both survive. Log once per streak when the channel worker itself downgrades an answer by dropping malformed rows, and name that cause in the shared contract, the user guide and the adapter's hint. Make the failure-path cold-empty cap message say the lane stays closed while no snapshot has ever succeeded, and align the design docs' risk summary with the ever-bound cost ceiling. * fix(channels): attribute the cold-empty cap by counted failed lookups Follow-ups to the PR bindings sidecar scan review (#16241, round 6). - The GitHub channel's cold-empty cap line attributes the stall from the failed lookups counted within the streak. It used to read boundSnapshotFailureStreak, which any successful answer resets, so a run of failed lookups broken by a single empty partial was blamed on the daemon's storage. The majority cause leads and the other count is kept. The same line now says the worker reports how many rows it dropped rather than naming one. - The bindings route falls through to the live createdAt when updatedAt is present but unparseable, instead of discarding both. - Docs: the hidden-catalog "excluded without partial" promise is scoped to sessions whose ownership can be read; the retention rationale describes the sidecar projection topped up from the bridge and trimmed to the cap; the user guide's list of partial causes includes a chats directory the daemon could not list. * fix(serve): key the bridge top-up by PR number and ignore restore time - The bindings route tops a listed row up from the bridge by PR number alone, as the session list's merge does: the sidecar writers replace an entry by number when it is re-bound to another repository while the bridge keeps the replaced entry, so the repository-keyed merge returned both and kept delivering the replaced PR's events privately. - For a persisted session the live createdAt no longer counts as activity: the bridge stamps a fresh one on restore, so merely restoring an idle follower could change which session receives a PR's events. The createdAt fallback is reserved for a session with no persisted record yet. |
||
|
|
13e65c1a6e |
feat(web-shell): link GitHub PR/Issue targets in channel events (#16226)
* feat(web-shell): link GitHub PR/Issue targets in channel events The channel events page showed the repository#number target of GitHub journal events as plain text, so operators had to copy the reference into a browser to inspect the PR or issue behind an event. Chat prompts and the PRs panel already link out; this closes the last unlinked surface. The URL is synthesized in the client from the channel config and the journal fields the GitHub adapter already records, so existing journal history becomes linkable without any daemon or journal change. The origin mapping mirrors the adapter's web origin derivation (github.com, GitHub Enterprise /api/v3, ghe.com data residency), and links pass the same safety and desktop-opener routing as chat links. Non-GitHub channels, removed channels, and malformed references stay plain text. * fix(web-shell): link GitHub events of channels inherited from user settings The events page resolved link metadata from the management listing, which is scope-local: a channel configured in user/system settings with cwd pointing at the project (the setup the GitHub channel guide recommends) has journal rows on the page but no listing entry, so its targets stayed plain text. The journal route now attaches non-secret link metadata (platform type and API base URL only) resolved from the merged settings the workers actually run with, filtered by the same cwd-ownership rule the worker grouping uses so one workspace never sees another's channel metadata. Resolution failure degrades to unlinked rows instead of failing the journal read. The page falls back to this metadata when the listing has no entry, and a source contract test now ties the client origin mapping to the adapter's so the deliberate mirror cannot drift silently. * fix(web-shell): make channel event link metadata safe and authoritative Review of the journal-metadata round found the default settings loader leaking the queried workspace's .env into the daemon's process environment (and able to rewrite a corrupt settings file) on a read-only polling route, and the client preferring the scope-local listing over the merged metadata even though system settings override workspace values. The route now resolves metadata through readSettingsSnapshot on the runtime's effective environment — side-effect-free, corruption throws into the existing degrade path — and the helper requires that view instead of defaulting to loadSettings. The page prefers the journal's merged metadata, keeping the listing as the older-daemon fallback, derives the displayed number from the thread exactly like the URL does, and strips trailing slashes from the base URL on both sides of the origin-mirror contract. The serve protocol doc now documents channelLinkMeta. * fix(cli): resolve channel link metadata against the boot environment snapshot The serve process-env guard flagged the journal route's live process.env fallback: a read-only polling route must not couple to shared mutable process state. Thread the daemon's frozen boot-time environment snapshot through the route deps and use it as the parent-process fallback, the same source the session host resolution already uses, instead of reading the live environment or adding a guard exception. * fix(web-shell): emit the resolved GitHub web origin from the daemon Review round 2 flagged that the web-origin mapping had two owners pinned only by a source-scraping mirror test, and that the boot-environment guard had no test able to fail. Retire the second owner: the channel package now exports resolveGithubWebOrigin as the single mapping owner, the adapter and the journal route both use it, and the route emits the resolved origin as channelLinkMeta.webOrigin so the browser only concatenates. The client's baseUrl derivation remains solely as the older-daemon/listing fallback, and the scraping contract test is replaced by a mapping-owner table (including whitespace-padded env-resolvable values), route origin assertions, and emitted-origin consumption tests. Pin the boot-snapshot guard behaviorally: mount() accepts a daemonEnv override and a new case resolves channels from a boot home that disagrees with the process environment — reverting to a live process.env read now reds that case. Also call the exported resolveChannelOwnerCwd instead of re-implementing the ownership predicate, and correct the protocol doc's read-scope wording for the added settings snapshot. |
||
|
|
e2d3243863 |
fix(web-shell): express the PR watch events dependency in the channel editor (#16236)
* fix(web-shell): express the PR watch events dependency in the channel editor The GitHub adapter rejects a non-empty prWatchEvents unless deliverToBoundSessions is enabled, but the editor never expressed that dependency: operators could tick watch events, press Save, and receive a raw English daemon refusal that read as "the selection cannot be saved". Group the bound-delivery switch and the watch-event checkboxes into one framed panel, disable the checkboxes with an inline hint until the switch is on, and mirror the runtime rule as a client-side validation backstop for hand-written stored configs that carry events without delivery. * fix(web-shell): keep stored watch selections clearable while delivery is off Review follow-ups on the bound-session panel: - A stored selection stays uncheckable-while-gated no more: with delivery off, selected events (including unknown stored values) can be cleared directly, while new selections remain disabled, so turning delivery off never strands the operator between a dependency error and disabled boxes. - Turning the delivery switch on clears the stale requiresBoundDelivery error instead of leaving the instruction under the now-on switch. - The disabled hint moves into FieldShell's existing hint slot, restoring the "description is the first paragraph" invariant; the hint gets an id, aria-describedby on the checkbox group and a persistent polite live region so screen readers hear why the boxes are gated. - The render gate now carries the same github scope as the validation mirror, and checked-but-disabled rows keep the selected border. - Tests: shutdown path, stale-error retirement, aria association, and a stored delivery-on mount; visuals scenario seeds the bound-session fields and captures the gated and enabled states. * fix(web-shell): scope dependency-error retirement to the dependency message The switch-on clear deleted the whole prWatchEvents error slot, wiping the higher-precedence invalidOption message while an unknown stored value was still ticked; and unticking the last selected event — the other remedy — left the dependency alert over an empty, valid selection. Retire only the dependency message, from both remedies, and pin the live region's persistence (empty region mounted before the hint, same node across the transition) plus the hint-less rendering of the pre-existing hint slots. * fix(web-shell): retire watch-events errors by re-deriving validity Round-4 review follow-ups, all in this PR: - R3-1: replace the string-matched dependency clear with a per-field re-validation on edit, so unticking an unknown stored value retires the invalidOption instruction it satisfies (and a remaining valid event swaps the slot to the still-unsatisfied dependency message). - R3-3: submit now scrolls the first erroring field into view even when it is not an advanced field; a stored watch-events refusal below the fold is no longer invisible. - R3-2: pin the empty-panel guard with EN/zh absence assertions on a non-GitHub descriptor. - R3-4: table-driven parity test feeds the plugin's own descriptor and config through both the editor mirror and the daemon validator (test-only devDependency, never bundled); a lone change on either side now fails the table. Each fix carries a mutation witness; 286 channel tests green. * fix(web-shell): re-derive only shown errors, in both switch directions Round-5 review follow-ups, all in this PR: - R4-1: refreshErrors re-derives only the error keys already on screen, so a remedy retires exactly the instruction it satisfies without surfacing validation the operator never submitted (submit stays the only population site), and the delivery switch re-derives in both directions so a shown instruction is never silently dropped. New tests pin pre-submit silence and shown-message survival across switch toggles. - R4-2: narrow plugin.management once at module scope in the parity test so the optional contract member stays TS18048-clean if test files ever join the package typecheck, with a clear error instead of a load-time TypeError. - Deferred note from the same round: pin the data-disabled styling hook on gated rows (dimmed) versus clearable stored rows (selected styling). Each fix carries a mutation witness; 288 channel tests green. --------- Co-authored-by: verify-round <verify-round@local> |
||
|
|
98ef7b2cd3 |
fix(managed-agent): reject dangling Hosted cleanup references (#16275)
* fix(managed-agent): reject dangling Hosted cleanup references * fix(managed-agent): honor JSONL framing for cleanup admission * test(managed-agent): align CWD migration lock witness |
||
|
|
e838c4c0d5 |
test(acp): expect the scheduler's plan-mode notice on the shared loop (#16305)
* test(acp): expect the scheduler's plan-mode notice on the shared loop With experimental.unifiedAgentLoop on, ACP's shared tool batch fails a call that plan mode blocks with the scheduler's notice, the one the model gets in the Ink TUI and headless, instead of ACP's own. The nightly unified leg failed on the integration case that pinned ACP's wording. - The integration case expects the notice of the loop it runs on, read from the variable the nightly leg sets for every CLI it spawns. - The ACP parity suite gets a plan-mode case: both loops block the write before it runs and ask for no approval, and only the notice differs. - The design doc lists the notice among ACP's intended differences, in both languages. * test(acp): check plan mode leaves the file unwritten and the current loop's tool result * test(acp): compare the plan-mode case in full apart from the notice Swap each loop's notice for a placeholder and compare all requests and updates, pin that both notices point the model to exit_plan_mode, and check one file reading per run. |
||
|
|
ed6760aa2c |
ci: reclaim qwen-autofix workflow headroom (#16069)
* ci: reclaim qwen-autofix workflow headroom Co-authored-by: Qwen-Coder <[email protected]> * ci: keep the qwen-autofix anchors the shape pins slice on The headroom pass collapsed three comment blocks and merged two `Full rationale` pointer lines. The workflow-shape tests use those exact comments as slice boundaries for the real bash they execute, and the pointer parser reads only the first `qwen-autofix.md#af-NNN` per line, so four pins went red on Test (ubuntu). Restore the `# Convergence-break mirror` and `# Convergence-signal circuit breaker (#10107): the park action on` wording, and let every pointer id spell its own anchor. No behavior change; refresh .size-baseline and the doc byte count to the new file size. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmvyc99g07m * docs(ci): restore the rationale the autofix compression dropped R1-1: af-085 answered only about the allowlist, so the worktree-config file, its pairing with extensions.worktreeConfig, and the hooks-path resolution ordering read as dead code next to a KEPT allowlist entry. Restore both paragraphs in the anchor the yml already points at. R1-6: the age-sweep comment pointed at af-149, which documents heartbeat and pid liveness only, while the reclaim reason lived nowhere. State it at both sweep sites. R1-3: the design record hardcoded the workflow's byte count, which the ratchet can invalidate without reddening anything. Name .size-baseline as the single source of the number instead. The bytes R1-6 adds land in .size-baseline (451750), so the recorded size still equals `wc -c .github/workflows/qwen-autofix.yml`. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw0wf5wvb8 * fix(ci): repoint two hollow rationale pointers and restore two rationales Address the four blocking hollow-pointer findings on the autofix size extraction (each verified by extracting the anchor span at this head): - gh-config mint (~4980): af-112 documents the git preamble, not the gh pins; repoint to af-149, which carries the GH_HOST/hermetic_gh material. - heartbeat rm -rf (~3742): af-149 lacks the '-rf, not -f' doctrine; repoint to af-158, which states it verbatim. - eval-marker parse (~2756): restore the POSITIONAL field-order rationale (.[0]=ts .[2]=round .[3]=win .[4]=run; every write site emits that order) into af-159. - WORKDIR cleanup (~1584): restore the 'last step, after every reader including the artifact upload' ordering rule into af-148. The two repoints are same-length, so qwen-autofix.yml stays 451750 bytes (.size-baseline unchanged). Workflow meta-test suite 350/350 pass. --------- Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: yiliang114 <[email protected]> |
||
|
|
93fdebb8fb | perf(managed-agent): narrow default workspace admission locking (#16283) | ||
|
|
d3ef273950 |
fix(cli): gate project hooks behind an approval dialog (#14748)
* fix(cli): gate project hooks behind an approval dialog Hooks in a workspace .qwen/settings.json ran at startup with no prompt whenever folder trust was disabled, which is the default. Gate them like project MCP servers (#4615): a hash-bound, user-local approval stored in ~/.qwen/hookApprovals.json, asked for in an interactive startup dialog (ink and OpenTUI) and re-checked on /hooks reload. Headless runs leave unapproved project hooks unloaded and print a stderr warning. * fix(cli): address review findings on the project hooks approval gate - Only a command-line --yolo skips the gate; a workspace can set tools.approvalMode itself. disableAllHooks, bare and safe leave it unarmed. - Key decisions by the project that supplied the hooks, not the live cwd, so /cd and --worktree keep approvals; /hooks reload acts only on a gated session. - Treat null and non-runnable hooks blocks as no hooks; validate stored status; hash with null prototypes; write the store 0600 with forceMode; keep QWEN_CODE_HOOK_APPROVALS_PATH out of project .env files. - Dialog: sanitize repo-supplied text to one row, show env/headers/ allowedEnvVars/prompt, cap the list to the available height, Esc postpones instead of rejecting; ink /hooks marks withheld hooks disabled. - Gate the legacy merged-hooks fallback in getUserHooks too. - Headless warning names the recovery for a rejected block. - Fix llm.test.tsx stubs, the real-settings reload test and the ACP integration tests (pre-approve via a shared helper); add render tests. * fix(cli): address second review round on the project hooks approval gate - Hook registry tags a copy of each definition, so an approved block keeps its hash in a reused settings instance (ACP daemon) - /hooks reload re-reads the approvals file; Esc adds a transcript notice - Truncated hook values are flagged; prompt text gets the full budget - Dialog keeps at least three hook rows and one row per hook - Hooks listing and both /hooks browsers show withheld project hooks as awaiting approval - Integration helper keys and hashes through the CLI's own code - Tests for getSeparatedHookSettings, the headless warning, the project .env exclusion, and gate table titles; docs record the hash scope and the subagent frontmatter follow-up * fix(cli): keep hook registry identity; copy project hooks for the gate instead The registry copy broke managed hook runtimes, which map registry entries back to their definitions by config identity. Hand Config a clone of the project hooks block so the settings instance an ACP daemon reuses keeps its hash. * fix(cli): bind hook approval to the unresolved block; show what runs - Hash and display the hooks block before ${VAR} resolution so startup and /hooks agree - Show command env as KEY=value; matcher last; rows cut to width with a marker - Derive dialog chrome from the wrapped header at the real width - Neutral "not approved" label (pending and rejected) - Docs: frontmatter hooks and statusLine are not gated; ACP status has no reason * fix(cli): read the unresolved hooks block only when the resolved one is gated * test: approve project hooks in new tests that rely on them * fix(hooks): bind approval to referenced env values and harden the approval dialog - Hash and show the values of $VAR/${VAR} a project hook references, so a .env change re-asks - Read the approvals file fresh, merge on save, and keep hooks withheld when a save fails - Count only hooks the registry would run; show a prompt hook's model; cut by code point - Keep the dialog inside the list budget in both renderers, modal over shells in OpenTUI - Esc dismisses a block for the session; label rejected hooks separately in /hooks - Re-arm the gate when a --yolo session leaves YOLO; forward the store path into sandboxes * test(serve): allow the hook approvals path in the sandbox env guard * fix(hooks): bind project hooks approval to the workspace env sources - Hash the unresolved block with the workspace's own .env files and settings.env instead of parsing $VAR names from the hook text; the dialog no longer shows env values. - Gate and list hooks from the resolved block, worded from the unresolved one. - Re-gate a --yolo session on its first switch out of YOLO. - Skip the gate under a shell execution sandbox; pass separated hook scopes from the hosted and memory-import callers. - Approval dialog strings are English-only in both renderers. - Tests for the rejected label, dialog choices, reload ordering, function hooks and the absent headless warning. * test(integration): hash project hook approvals with the workspace env sources * fix(cli): keep Ctrl+C out of focused Ink shell input Fixes the foreground-shell flake in #15814. Same change as #15859. * test(managed-agent): stop the verification suite running out of heap on hosted runners Raise the surefire fork to 40% of RAM and make the lease renewal test wait for renewals instead of counting them in a 150ms window. See #15248. * fix(cli): re-gate project hooks against the live block when leaving YOLO The YOLO-exit listener re-applied the startup approval snapshot, so a block replaced by /hooks reload while YOLO-exempt ran unreviewed after leaving YOLO. Read the live block through a provider closed over the live LoadedSettings (the disabledSkillNamesProvider pattern), and keep listening so re-entering and leaving YOLO re-gates again. Closes review finding R7-1. * fix(cli): keep hooks approval options visible on short terminals The dialog chrome never clamped to the row budget, so on a short terminal the fixed-height frame clipped Reject and left only the pre-focused Approve. Cut the description to the rows the chrome and one list row leave, in both renderers, before shrinking the hook list. Closes review finding R7-2. * test(cli): build reload approval fixtures from the real env sources The fixture pinned an empty env while the production hash reads every project env file findEnvFiles discovers, including ancestors of the temp root, so the approval tests went red on runners with an ancestor .env. Read the same sources in the fixture and plant an ancestor .env so the hash coverage stays provably sensitive on every machine. Closes review finding R7-3. * fix(core): approval is execution authority for project hooks (either-or) The interactive folder-trust gate merged from main swallowed the approval decision: with the feature off (default), Approve persisted but getProjectHooks() still returned undefined, so the dialog promised execution it could not deliver (review blocking item 1; tmux Cases 1b/7). readProjectHooks() now admits the block when the hash-bound approval is approved OR the folder is explicitly trusted; pending and rejected stay withheld, and an ungated interactive session still needs folder trust. Tests pin interactive + feature-off + approved => runs, pending/rejected => withheld, ungated untrusted => withheld. Also from the same review (suggestions): the Ink /hooks browser now mirrors the execution accessor instead of showing trust-withheld hooks as enabled; a command containing variable references says it resolves them from the approved project env; the Esc notice points at restart instead of implying /hooks can decide. Co-authored-by: Qwen-Coder <[email protected]> * fix(cli): align hook status surfaces and docs with either-or authority The either-or execution gate left three status surfaces behind (review blocking items 1a-1c): the startup trust warning fired for approved blocks that actually load, getTrustWithheldProjectHooks() re-listed an approved block as trust-disabled (duplicate contradictory rows in the OpenTUI /hooks browser and ACP status), and the Ink browser's enabled expression showed ungated untrusted sessions as enabled. The warning now skips approved blocks, the listing accessor shares the execution predicate (hasApprovedProjectHooks), and the Ink row mirrors getProjectHooks() directly. Docs state approval-or-trust authority and the folderTrust-on-untrusted no-dialog case (blocking item 2). Tests: approved + default trust => no warning and no withheld copy; ungated untrusted => Ink row disabled; pending => trust row present. * fix(cli): warn from the execution accessor, not the approval flag The round-10 warning guard read gated && !pending as "approved", but pending === false also means "nothing to approve" (an untrusted folder with the trust feature on contributes no settings). That suppressed the one signal the user gets in exactly the case the warning exists for (review blocking item; also the Integration Tests no-AK failure). Skip the warning only when getProjectHooks() actually serves the block, and offer approve-or-trust only while a decision is genuinely pending. Rename the ambiguous core predicate to isProjectHooksApprovalCleared and document both meanings; getWithheldProjectHooks reads the raw block so the listing names a pending/rejected decision even when trust also withholds, with the approval reason outranking the trust reason and no duplicate rows. Scope the docs' YOLO execution claim to non-interactive runs. Tests: folderTrust-on untrusted warning regression (unit + the existing interactive integration case), approval-reason priority in the listing, raw-block reachability while pending. --------- Co-authored-by: 易良 <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
572ee4f6ba | fix(managed): Reap Git descendants before terminating parents (#16298) | ||
|
|
b3e9cc7c4d | fix(managed-agent): use current state for approval changes (#16296) | ||
|
|
33e3d39d50 | fix(managed-agent): use current state for approval changes (#16296) | ||
|
|
a5fec03684 |
fix(ci): fail Java lanes before disk exhaustion (#16068)
* fix(ci): fail Java lanes before disk exhaustion Co-authored-by: Qwen-Coder <[email protected]> * fix(ci): gate the MySQL pool lane instead of a hosted job The disk floor gate landed in hosted-harness-mysql, which runs on ubuntu-latest, so `runner.environment == 'self-hosted'` is never true there and the step was dead code. The job that actually shares the ECS pool disk - mysql-integration, two Maven suites plus a per-leg MySQL container - stayed ungated. Move the step to mysql-integration, immediately after its checkout so the sdk-java-workflow pin still holds. This also clears the three Hosted real-process gates pins, which reject a conditioned step among the setup steps that precede managed_dependencies in hosted-harness-mysql, and reflow the new test to the repo's prettier style. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmvya43o17i * style(ci): drop the blank line left behind in hosted-harness-mysql Moving the disk floor gate out of hosted-harness-mysql left a stray blank line between its checkout and 'Set up Java'. Restoring it makes the whole workflow diff additive on mysql-integration only, so 'hosted-only jobs are unchanged' holds byte for byte and an A/B census of that job reads clean. Co-authored-by: Qwen-Coder <[email protected]> * ci(sdk-java): record the real workflow size after the disk-floor gates The six #16003 disk-floor gate steps grow sdk-java.yml from 154074 to 155650 bytes. main had already drifted 3742 bytes past the recorded 150332 — inside the 4096 allowance, but it left only 354 bytes of ratchet headroom, so this real growth tripped Lint & Static. Record the file's actual size rather than shrinking the gates: each one is a three-line step calling .github/scripts/check-disk-floor.sh, and the workflow test pins all six gated jobs. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw02ewzoa2 * fix(ci): derive the disk-floor corpus from runs-on and narrow the broker claim Address the two blocking review findings on the Java disk-floor gate: R1-1: the meta-test skipped any job without the gate and compared a closed six-name list, so a newly pool-routed lane that forgot the gate stayed green, and a neutered gate (continue-on-error) kept every assertion true. Derive the corpus from each job's runs-on (same hazard as the setup-java corpus), require gateIndex > -1 per pool-capable lane, and assert the gate carries no continue-on-error. The derived set is the same six jobs today. Verified by mutation: adding continue-on-error to a gate reddens it, and renaming a gate (ungated pool job) reddens it with 'expected -1 to be greater than -1'. R1-2: the step comment claimed RUNNER_TEMP covers the broker store, but the absolute free-KiB floor cannot express the broker's used-ratio refusal (a 952 GiB volume at 94% used still has ample free bytes). Narrow the comment to the gate's real scope (near-full filesystem / inode exhaustion, #10035) and state the broker disk-threshold investigation stays out of scope, matching the PR description. Bump .size-baseline for the comment change (155650 -> 155888). sdk-java workflow meta-test 117 passed / 5 skipped; eslint exit 0, prettier clean; check-workflow-size.sh passes. * fix(ci): calibrate the Java disk floors and fail closed in the meta-test Review round on this PR found the gate could not fire in the two shapes it exists for, and that the meta-test could go green through the same drift it was added to prevent. R1-2: every lane ran the shared 2 GiB / 100k-inode default. The #16003 incident was a used-ratio refusal — the broker reported 0.94 while the 952 GiB volume still had tens of GiB free — so no absolute floor can express that threshold, and 2 GiB is below what these lanes write: a host with 4 GiB free passed the gate and then died mid-suite. Give the five heavy lanes the repo's build-sized 8 GiB floor, leave flyway-migrations and the shared value alone, and add an opt-in DISK_FLOOR_MAX_USED_PCT arm to check-disk-floor.sh for the two broker lanes (90%). R2-1/R2-2: the corpus came from `runs-on` text and skipped jobs outside it, so a lane routed through an expression or a job output shipped ungated while the suite stayed green, and nothing asserted the hosted-only jobs carry no gate. Fail closed: an explicit hosted-only allowlist, a gate required for every other job, a total-count assertion, and a negative assertion on those two jobs. Pin the per-lane floors so dropping one is a test failure. Verified: sdk-java-workflow suite 117 passed, 5 skipped; it goes red when a gate is deleted, when one is added to a hosted job, and when a floor is removed. check-disk-floor suite 12 passed, including four new ratio cases. --------- Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: yiliang114 <[email protected]> Co-authored-by: qwen-code patrol <[email protected]> |
||
|
|
b383efb854 |
fix(ci): widen the built-tree download window for eight shards (#16285)
* fix(ci): widen the built-tree download window for eight shards Eight shards now pull the same unit-test-tree artifact at once, so the download cap has to clear the blob's tail latency rather than the median. On main the slow host class took 4m52s, and two consecutive runs expired twice at the five-minute cap before the unpack ran, which failed the required Linux check with no test involved. Raise both attempts to fifteen minutes and pin the pair in the build-to-shard handoff contract. Co-authored-by: Qwen-Coder <[email protected]> * fix(ci): size the shard download cap from the measured tail Review flagged that the outgoing comment claimed a pool host had stalled "for over a quarter of an hour", which the new fifteen-minute cap then contradicts. That claim came in with the sharding commit and was never tied to a run; neither incident can prove how far past five minutes the tail went, so the comment now states only the measured distribution. Also correct the number: the slowest completed shard took just under five minutes, and it was a different shard that expired, on two consecutive runs. Add the missing shard-side ceiling contract, mirroring the build job's: every blocking step cap has to fire before the job cap, because a cancelled job skips the failure-gated disk-pressure collector. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
26ed1a322b | fix(managed-agent): enforce strict SQL mode per embedded connection (#16288) | ||
|
|
01bbffb17f | fix(cli): derive context tool details from declared schemas (#16292) | ||
|
|
776331b1f9 |
fix(mods): skip hooks on ui.backdrop instead of failing (#16289)
A Mod that hooked ui.backdrop failed to initialize, which stopped a headless run before any model request. It is now skipped with the startup line, like other events Qwen Code does not run yet. $.session.version() reports 2.1.318. |
||
|
|
641e258546 | fix(daemon): automatically discover config Skills changes (#16262) | ||
|
|
815c1d3768 |
feat(web-shell): collapse channel editor tuning behind advanced settings (#16239)
* feat(web-shell): collapse channel editor tuning behind advanced settings The shared channel editor renders every descriptor field flat, so a fresh GitHub instance shows roughly sixteen controls and the fields needed for first setup are buried under rarely changed tuning. Move the tuning fields into one collapsible advanced panel and hide the default message route until a route exists, keeping the default view to identity, credentials, conversation isolation, and access control. Collapsing never hides state: the panel opens automatically when any advanced field is configured, shows a count badge while collapsed, and forces itself open when a collapsed field fails validation. Save and validation semantics are unchanged because both already iterate every descriptor field regardless of rendering. The watch-events dependency UX is left to #16236 so the two changes stay complementary. * fix(web-shell): forward refs through the collapsible wrappers Review of #16239 showed the three new wrappers accept Radix props including ref, but plain function components drop external refs on React 18, and the repository supports both React 18 and 19. Use React.forwardRef for the root, trigger, and content, pass each ref to its Radix primitive, and pin the contract in react18-ref-compat.test.tsx with both forward-ref type and DOM-ref assertions; a plain-function mutation turns exactly the three new type assertions red. Record the contract in both design documents as well. * fix(web-shell): keep endpoints and operators visible, make collapsed errors reachable Review round 1 on #16239 found the disclosure went too far in three places and left dead scaffolding behind. Primary endpoints (baseUrl for GitHub Enterprise / self-hosted GitLab, wsUrl for WeCom) decide where a token is sent, so they stay in Connection instead of collapsing; a fresh enterprise channel could otherwise save without its endpoint and send the token to the public API. Operators is a permission grant and stays in Access control, a deliberate product decision rather than a consequence of the key set. The panel title becomes a real h3 wrapping the trigger (ARIA makes button descendants presentational, so the previous span-in-button was invisible to heading navigation), and a rejected submit now scrolls the first erroring advanced field into view instead of only opening the panel below the fold. The record rule is unified into one exported predicate shared by validation, the save path, and the visibility/auto-open readers; the unreachable routing section scaffolding is removed; a comment records why enum keys must stay out of the advanced set. Each new mechanism carries a mutation witness (endpoint placement, heading, boolean arm, record arm, scroll), and the visuals scenarios now anchor the heading, the configured-instance auto-open, and the count badge in a real browser. Both design documents are revised in lockstep. |
||
|
|
30079d6c3d |
test(managed-agent): ratchet persistence and runtime-broker class sizes (#16284)
The store decomposition (#16090, #16142) and the broker extraction cluster turned the god classes into facades, but nothing stops them regrowing. Add ClassSizeRatchetTest, the class-size gate the structure review left as an open question, introduced now that the splits have landed. It pins every current class >= 800 lines in persistence/runtime-broker at its present size (ratchet-down-only), caps all classes at a global ceiling seeded at the current maximum, forces any class reaching 800 lines to be pinned, and rejects stale pins. Verification-only: no production code change. Each rule is mutation verified; the bilingual design and the structure-review ledger (open question resolved, acceptance criterion concretized) are updated in the same change. Co-authored-by: verify-round <verify-round@local> |
||
|
|
eefd4fd654 |
test(e2e): accept both plan-mode wordings in the ACP case (#16273)
`acp integration > blocks write tools in plan mode` filtered tool updates
with `includes('Plan mode')` and asserted `'Plan mode is active'`. The
unified agent loop blocks through the core scheduler, which says
`Tool blocked by plan mode: ...` (lowercase), so the case-sensitive filter
found no event and the nightly unified leg failed while every push leg
stayed green (run 41771749222).
Match `/plan mode/i` and keep the file assertion that proves nothing ran.
Verified locally: the case fails on origin/main with
`QWEN_CODE_ENABLE_UNIFIED_AGENT_LOOP=1` and the same message CI reports,
and passes both with and without that flag after the change.
|
||
|
|
ad47896d85 |
test(interactive): share one CI-scaled wait policy across the TUI suites (#16276)
Nine interactive suites each declared their own polling helper with a fixed budget (8s, 12s, 15s, 20s) and no allowance for where the suite runs. The shared ECS pool runs several Actions runners per host, and hosted macOS VMs are slower still, so an assertion that passes in ~12s locally expires mid-repaint in CI. The current open reports all have this shape: tui-foreground-shell (#15712, #15814, #16046), mid-turn-submit (#16250) and mods-tool-middleware (#16040). Move the helper to one module and scale its budget 3x when CI is set. A stuck UI still fails on the same assertion, just later; no assertion or product path changes. mid-turn-submit's 30s request wait gets the same treatment: it exceeded that wait at 97s on a loaded host. Verified locally: tui-foreground-shell, mid-turn-submit and tui-editor-parity pass (33 passed, 1 skipped), eslint and the integration typecheck are clean. |
||
|
|
d99bb685dd |
test(web-shell): wait for the restored card before clicking continue (#16286)
The nightly `web-shell Browser Regression` dies in the generated-UI retention case at `expect.poll(() => text).toBeDefined()`: the click on `#continue-analysis` produces no prompt request, so the capture route never fires (run 41771749222, job 126382801964). The same case passes on a dev machine — 6/6 runs here — so this is a host-timing shape, not a product defect. Wait for the card's continue control to be enabled, give dispatch a CI-sized budget, and fail with the card and status state when nothing is dispatched. The next nightly then names the cause instead of `undefined`. |
||
|
|
fb8aa7fa3f | fix(core): accept images for Qwen2-VL, Qwen2.5-VL and QVQ models the catalog does not list (#16247) | ||
|
|
64b499f42a | fix(core): recognize Hugging Face TGI context overflow errors so reactive compaction runs (#16245) | ||
|
|
a85370b38f |
feat(cli): run the Ink TUI's model rounds on the shared loop's round (#16206)
* feat(cli): run the Ink TUI's model rounds on the shared loop's round With experimental.unifiedAgentLoop on, every model round of the Ink TUI goes through the shared loop's runModelRound, which collects the tool calls, drops those of an attempt a retry or fallback replaced, and turns the client's terminal events into a stop reason. The TUI keeps its stream processor and its tool step. Adds the Ink parity suite. Part of #15327. * test(cli): tighten the Ink parity suite and fix the no-AK script pin - add the suite to the no-AK script pin - check each run's request count, and through the debug log that only the run with the setting on sends its rounds on the shared loop - compare the system prompt, the tools offered and recorded usage - run each scenario once, without the root config's retries, with a source pin - describe the suite as it is in the design doc - say in the setting's description that the Ink TUI sends its model requests through the shared loop |
||
|
|
0c93377886 |
feat(mods): run prompt.submit hooks in the OpenTUI terminal (#16195)
* feat(mods): run prompt.submit hooks in the OpenTUI terminal The OpenTUI live turn now raises prompt.submit for each prompt the person types, before its row, the input history, @ references and the send. A rewrite stands in the sent prompt, the row, the input history, the provenance and turn.start; images stay attached and a paste kept whole stays collapsed. A drop shows its reason and hands the prompt back to the composer. While a Mod hooks prompt.submit, queued prompts wait for the turn to end instead of steering it, so they pass the hooks too. * fix(mods): keep a returned prompt while a dialog hides the OpenTUI composer A prompt the prompt.submit hooks drop, or one Esc cancels without a composer to settle with, went back through the composer handle. A dialog opened mid-turn (/settings, /theme and other commands that run while a turn responds) unmounts the composer, so the handle was empty and the prompt, never shown or recorded, was lost. It now waits on the entry's composer slot and returns when the composer mounts again. |
||
|
|
14a5a2d98d |
fix(core): preserve Gemini thought signatures for tool replay (#16270)
Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
56fec7f3f5 |
ci: shard the Linux unit tests and web-shell smoke, and reuse cached builds (#16133)
* ci: shard the Linux unit-test lane eight ways behind one build The Linux Test job ran 45-59 minutes: a ten-minute prepare build, then the CLI and core suites back to back. A new test_build job runs the prepare build once and uploads its outputs; eight shards install without the build, unpack them and run --shard=N/8 of every Vitest suite. Test (ubuntu-latest, Node 22.x) becomes an aggregate that passes only when the build and every shard pass. * ci: record ci.yml's growth from the test_build prelude test_build carries the same pre-checkout cleanup, checkout and install prelude as the test shards; ci-platform-lanes.test.js pins the two copies identical. * ci: record the Linux lane's two new jobs in the PR-stage budget test_build and the test_ubuntu aggregate add two jobs; the declared timeouts drop from 305 to 295 minutes. * ci: shard the web-shell E2E smoke four ways The smoke lane ran 44-46 minutes on every recent PR, about 33 of them in the browser tests at two workers, and is the longest lane once the Linux unit tests are sharded. Each shard runs --shard=N/4 of the smoke; the transcript gate runs on the first. web-shell E2E Smoke (ubuntu-latest, Node 22.x) becomes an aggregate over the shards. * ci: bound the unit shards' tree download and retry it once A pool host stalled 17 minutes in download-artifact while the other shards took under a minute. * ci: rebuild only the workspaces a change reaches scripts/ci-build-cache.mjs hashes each workspace's tracked build inputs (tests excluded) with its workspace dependencies' hashes and the shared root inputs. test_build and the web-shell smoke shards restore the newest cached build and rebuild only workspaces whose hash changed. A push to main always builds from scratch and saves, so a pull request starts from a clean build of main. * docs(ci): describe the incremental build cache * ci: keep the build cache path the same on every pool host actions/cache versions an entry by its path; under runner.temp the path differs per host, so a run could never restore another host's save. * ci: save the ARM64 build cache from the smoke lane, and run the aggregates on the shards' runners The smoke shards run on the ARM64 pool, which test_build may not, so the first shard saves the clean build it made when its restore found nothing. The two aggregate checks queued 9 and 13 minutes for a hosted runner after their shards finished; they now take the shards' runners. * ci: bound the sharded test step and skip shards when the build fails Address the first automated review round on the Linux sharding lane: - Gate every build-consuming shard step on `test_build` success. `!cancelled()` stays true when a needed job fails, so a failed build still started all eight shards, each paying a full install before dying at the missing artifact and reporting artifact plumbing as its failing step. - Drop the test step cap to 45 minutes. The job cap is 60, so the old 110 could never fire; a step timeout keeps the diagnostic steps below it alive while a job timeout does not. - Keep the built-tree artifact for a week so a late "re-run failed jobs" still finds it, and correct the trigger comment that still named three post-merge jobs when five now accept push. Co-authored-by: Qwen-Coder <[email protected]> * ci: keep disk telemetry for a failed build and correct the design doc `test_build` installs with the disk sampler and then builds for up to its whole timeout, but the sampler dies with the install step's trap and no collector ran, so an ENOSPC build (or the OOM that took a shard host) kept no evidence. Give it the same failure-gated dump and last-step upload as `lint_and_static`, under its own artifact name. The incremental-lint design still said the test job keeps normal prepare behaviour; this lane now installs with `QWEN_SKIP_PREPARE=1` and unpacks the tree built once by `test_build`. Correct both language versions. Co-authored-by: Qwen-Coder <[email protected]> * ci: share the built-tree definition and the build preflight, harden the guards The archive step re-implemented `builtPaths()` from `scripts/ci-build-cache.mjs`, so the tree the shards unpack had a second definition that could drift from the one `pack` stores. A `paths` mode now prints that list for `tar -T -`, and a new test pins the four artifact-name literals that tie the producer to the eight consumers. The cached branch of `build` also hand-reproduced a subset of `scripts/build.js` and so skipped its ink-patch preflight: the same missing `frame-controller.d.ts` self-healed on a cache miss and surfaced as ~50 tsc errors inside ink's declarations on a hit. The preflight is now a function reachable as `scripts/build.js --preflight` and runs before both branches. The remaining review suggestions were all meta-test gaps: the standalone-runtime expression and the `test_build`/`test` prelude were guarded by nothing that compares them in order, `ci-build-cache.mjs`'s three CI modes were never executed by a test, and the infra-classification filter's step name was not joined to `ci.yml`. Each now has an assertion that goes red when the guard is removed. Co-authored-by: Qwen-Coder <[email protected]> * ci: bound the build steps so the failure collectors can run Round-2 review found four issues in the previous round's fixes and one gap in the new build tests. A job timeout cancels the job and skips the failure-gated collectors, so the `test_build` steps that can block now carry caps below the job's 45 minutes: the install, the build, the pack and the archive. The install caps are identical across `test_build`, `test` and `lint_and_static` so the prelude parity guard still compares the three copies field for field. A test pins every blocking cap under the job ceiling. The `--preflight` early exit was only asserted as a string, so it could be renamed with nothing going red while the cache-hit path silently ran the full build. A spawned case now copies `scripts/build.js` into the fixture and asserts the flag exits without generate, a workspace build or the schema step. The CLI mode tests write extension-less `#!/bin/sh` stubs and join PATH with ':', which cmd.exe cannot resolve; the Windows lane collects that file and downgrades its failure to a warning, so it would have carried a permanently red block. The suite is excluded on win32 alongside the other bash-fixture suites. The `test_build`/`test` intersection grew to 16 with the disk collector, but the floor stayed at 15, leaving exactly one shared step uncompared; the floor now matches the membership. The cache-hit-with-changes branch — the code that decides what is rebuilt and what is deleted — gains a case: a changed workspace is purged and rebuilt by directory, and an untouched workspace keeps what the cache restored. ci.yml's recorded size baseline is updated to the current file so the next PR gets the full growth allowance back. Co-authored-by: Qwen-Coder <[email protected]> * test(ci): keep cache fixture call logs outside the archived tree Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: 易良 <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
4d5e30e6f0 |
fix(test): wait for the RocketMQ port listener to clear after stop (#16252)
Aligns the parked-port release assertion with the script's own listener check: on a shared pool host a fresh bind probe can fail for a foreign socket or race the killed listener's asynchronous socket release (release run 41708821484, Scripts lane). The sibling case already uses waitForNoListener. Refs #16248 |