Commit Graph
11492 Commits
Author SHA1 Message Date
jinyeandQwen-Coder 1876eb4997 test(managed-agent): Add shared Harness Kubernetes qualification tools (#15519)
* test(managed-agent): Add Kubernetes pool qualification tools

Build from immutable source, constrain deployment to explicit business workers,
and verify deployment identity plus signed multi-tenant public flows.
Include the cross-Pod foreground publication configuration exercised on ACK.

Co-authored-by: Qwen-Coder <[email protected]>

* style(managed-agent): Match Kubernetes qualification YAML lint rules

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-25 20:48:38 +08:00
易良 517ba13ad0 test(managed-agent): pin the row pool's fan-out, correct its design claims (#15528)
* docs(managed-agent): state the row pool's real shutdown and row bounds

The design pair claimed the pool never parks the context close and that
SCAN_LIMIT is a per-tick row bound. The pool's destroy does return
immediately, but a tick caught mid-join still holds the close until the
application's shutdown phase expires, and one tick submits two queries'
worth of rows (deduped), not one.

* test(managed-agent): pin the row pool's dedupe guard and interrupted join

A mutant that removes the armed/firing dedupe survived the suite, and
nothing exercised the join's interrupt arm. The backlog case now asserts
the completed-task count instead of a pool gauge sampled after the fact.

* test(managed-agent): read the backlog drain off the joined count, not a pool counter

ThreadPoolExecutor increments completedTaskCount after a task's future
unparks the joining thread, so the counter can lag the tick's own answer;
the joined fire count already proves the backlog drained. Recorded on
review as a low-severity race in the previous assertion.

* docs(managed-agent): name the scheduler bean behind the shutdown bound

The shutdown sentence said "the application's shutdown phase"; the bound
belongs to the scheduler bean that invokes the tick, which is the
SmartLifecycle the lifecycle processor waits on. Also list the two new
tests where the validation section lists the others.

* test(managed-agent): pin that the armed task records its due slot

Claiming once proves the row was submitted once, but a tick that kept only
the firing task would also claim once and never derive the slot that came
due meanwhile. Assert the due slot is recorded, which only the armed task
does — the dropped-armed-task mutant now fails here.

* docs(managed-agent): call the shutdown phase a bound, not a fixed wait

The lifecycle processor caps the scheduler bean's stop at
timeout-per-shutdown-phase; it does not necessarily wait the full phase
out before interrupting a mid-join tick. Say upper bound.
2026-10-25 20:45:35 +08:00
易良 75c9e5ef69 test(memory): pin recovery-path and hard-rescue catalog accounting (#15535)
Land the permanent coverage that the review verified only with temporary
witnesses: the output-recovery request keeps the superseded snapshot off the
wire while carrying the current catalog once, its output budget is computed
from that catalog-bearing payload when no usage arrived, and a pending
non-empty catalog is what pushes the post-compression estimate over the hard
limit, aborting the send.

Each assertion fails when the corresponding retention flag flips or when the
estimator drops the pending snapshot.
2026-10-25 20:45:29 +08:00
Shaojin Wen afd0bdb376 fix(cli): keep a Web Shell /autofix-pr watch alive through an idle session (#15523)
* fix(cli): keep a Web Shell /autofix-pr watch alive through an idle session

A daemon session is reclaimed once nobody has watched it for the idle
timeout, taking the in-memory scheduler of /autofix-pr's session-only
task with it: the watch stopped with no report, and the Web Shell's
scheduled-tasks page never listed it.

`autofix-pr schedule` now prints `durable: true` with
`sessionMode: "current"` when it runs in a daemon session, so the watch
is a durable task bound to that conversation: the daemon keeps the
session resident, rehydrates the task after a restart, and the page
lists it. A terminal session keeps the session-only task. The skill
creates the task from what `schedule` printed and falls back to a
session-only one when a daemon cannot bind a task to its session.

Tested: packages/cli schedule.test.ts (8), packages/core SKILL.test.ts
(5), eslint, prettier.

* fix(cli): cover a daemon resume and name the watch's restart caveats

A daemon restart drops the gate's in-process approval, and closing the
browser tab does not end a daemon watch; the guide now says both. The
daemon test also asserts the durable pair on `schedule --again`, and
drops a save/restore the fixture already performs.

Tested: packages/cli schedule.test.ts (8), prettier.

* fix(cli): say a daemon conversation carries one watch

A conversation already bound to a scheduled task cannot take a second
one, so /autofix-pr on a second PR needs a conversation of its own. The
skill now names that refusal instead of reporting only that the watch
has no task, and the guides and the design note record the limit.

Tested: packages/core SKILL.test.ts (5), prettier.

* docs(autofix-pr): resume one watch per daemon conversation

A daemon conversation carries one scheduled task, so resuming several
watches there schedules only the first and leaves the rest without a
task. The guide now says to resume them one conversation at a time.

* fix(cli): keep another session's watch out of a bare stop or resume

Durable tasks are project-visible to cron_list, so a second conversation
in the same project sees the first one's task. Stop deleted every
matching task before cleanup checked ownership, so a bare stop in B
killed A's running watch; resume's "another session runs it" exclusion
keyed on the PR having no task, which A's visible task bypassed.

Stop now reads status first, leaves a task whose watch another session
still runs (unless the user named the PR), runs cleanup before any
delete, and deletes only for PRs whose cleanup ended the watch. Resume
treats a task naming the watch's session with thisSession false as
another session's running watch: a bare resume excludes it and the
confirmation warns about it. A named PR still takes the watch over with
--take-over.

Tested: packages/core SKILL.test.ts (5), prettier.
2026-10-25 20:29:39 +08:00
Shaojin Wenand易良 e7a67ee4e3 feat(managed-agent): give the Hosted Harness its Broker through the environment (#15244)
* feat(managed-agent): give the Hosted Harness its Broker through the environment

The Hosted Harness reached the Runtime Broker only through command-line
options, so the shipped unit expanded the Broker token into argv, where
any local user can read it, and the token was stored again in
server.env for the control plane. A busy Harness port also moved the
Harness to another port that the control plane could not reach.

harness.env now carries QWEN_MANAGED_RUNTIME_BROKER_URL and
QWEN_MANAGED_RUNTIME_BROKER_TOKEN, which qwen serve already reads and
deletes from its environment, and the unit no longer passes Broker
arguments. Every secret gate lists the token too: core's child and
${VAR} denylist, the ACP scrub list, the channel worker environment and
the control plane's Runtime worker filter. The Hosted Harness profile
fails on a busy port instead of moving.

The Harness credentials file may carry the Broker token for the control
plane, refusing a second direct copy and binding nothing on a conflict;
both Broker lines follow the strict credential rules. Preflight adds a
broker-url row that compares the file's URL with the Broker's address
and warns when the file has none, and the broker row's fix names the
file only when startup read it. The bootstrap, templates, systemd unit
and guides keep the token in harness.env only and tell upgraded hosts
to replace QWEN_HOSTED_HARNESS_BROKER_ARGS.

* fix(managed-agent): check the Harness Broker URL against the actual listener

The broker-url preflight row matched loopback addresses by class, so a
Broker bound to 127.0.0.1 passed a file URL on 127.0.0.2. It failed any
URL that differed from the bind address, although a proxy or TLS front
end may forward it. And it ignored the scheme once a port was given, so
https to the plain-HTTP Broker passed.

The row now passes only a direct http URL: the Broker's port, and a host
that resolves to the bound address itself or, for a wildcard bind, to an
address of this machine. https to that same listener fails, since the
embedded Broker serves plain HTTP. Any other URL warns that a proxy may
forward it, which preflight cannot verify, so it no longer fails --check.

* fix(managed-agent): hold the Harness Broker URL to the Harness's own rules

- broker-url first applies the rules managed-runtime-broker-url.ts applies
  at Harness startup (an http or https origin, plain http only on loopback),
  reads the host of a registry authority such as an underscore name, and
  names the direct loopback URL, or the TLS-terminating proxy for a
  non-loopback bind, in its fix.
- A broker check fails a non-loopback bind without
  runtime-broker.allow-non-loopback, which the Broker refuses at startup.
- The Harness credentials file requires the Broker URL and token together,
  refuses a key containing whitespace such as `export KEY=value` (systemd
  ignores it, a shell assigns it), and lets the URL bracket an IPv6 literal.
- qwen serve warns when --managed-runtime-broker-token is on the command
  line; serve tests cover the environment fallback, its removal and flag
  precedence.
- The upgrade runbook names the server.env token to delete, the server
  README lists the Broker prefix among the stripped ones, and the bootstrap
  test checks stdout for every generated secret.

* fix(managed-agent): read the Harness Broker URL the way the Harness parses it

The broker-url preflight row predicted the Harness with java.net.URI and a
hand-written loopback check, which disagree with Node's WHATWG URL parser in
both directions. It failed http://127.1:4182, http:/127.0.0.1:4182 and
http://127.0.0.1:4182/./, which the Harness takes as its loopback origin, and
passed http://::1:4182, http://127.0.0.08:4182 and port 99999, which the
Harness refuses before it starts.

- HarnessBrokerUrl follows the URL Standard for the part of an http(s) URL a
  credentials-file value can reach: the scheme, the slashes after it, the
  IPv4, IPv6 and ASCII-domain host parsers, the port and the path's dot
  segments. An xn-- label needs IDNA, so the row warns that it cannot read
  that URL instead of failing.
- managed-runtime-broker-url.fixtures.json records the Harness's verdict for
  each spelling; the CLI test holds the Harness to it and HarnessBrokerUrlTest
  holds preflight to it.
- The fix's direct URL is built from the address the bind resolves to, so a
  ::ffff:127.0.0.1 bind yields http://127.0.0.1:4182.
- Docs: the upgrade backs up server.env and harness.env and a rollback
  restores them; the guide names the row's no-URL warning; the operator pages
  allow [ and ] in the URL key; the server README states the URL/token pairing
  rule.

* fix(managed-agent): keep the Broker pair across a daemon update restart

A daemon self-update replaces `qwen serve` through execve, which keeps
the PID, so systemd never hands the replacement the Broker pair from
harness.env again, and serve has already removed the pair from its own
environment. Hand the update restarter the resolved pair and add it to
the replacement's environment only, beside the server token.

Also from the same review round:
- preflight decides the plain-http, path and ends-in-a-number refusals
  for a host with an xn-- label without IDNA, and leaves only an https
  URL to such a host unclassified;
- the Java fixture loop allows only the two documented approximations, a
  scheme or path read before the host and an xn-- host read without
  IDNA; the shared fixture gains cases for both;
- the CLI fixture loop fails on a verdict it does not know instead of
  passing on any throw;
- the broker-url row brackets an IPv6 Broker address;
- the credentials file refuses an empty Broker URL or token, which the
  Harness takes as set and then refuses to start with;
- the deployment guide no longer says a wildcard Broker bind is reached
  through a proxy.

* fix(managed-agent): warn on an IPv6 URL for an IPv4 wildcard Broker bind

The JDK binds 0.0.0.0 to IPv4 alone under -Djava.net.preferIPv4Stack=true,
so a Harness given http://[::1]:P cannot reach a Broker bound there, while
the broker-url row passed it as served. Preflight cannot see the server's
JVM options, which may come from an environment it does not share, so a
wildcard bind now serves only addresses of its own family, and an IPv6 URL
to the IPv4 wildcard warns and names the IPv4 loopback URL. An https URL
there still fails. The JDK binds :: only on a dual-stack socket, so the
IPv6 wildcard keeps serving IPv4 too.

* fix(managed-agent): warn on an https Broker URL across address families

When the Broker binds the IPv4 wildcard and the URL names an IPv6
address, the Broker's own socket takes it only from a dual-stack JVM.
Under -Djava.net.preferIPv4Stack=true another listener, such as a TLS
proxy, may hold that address and forward to the Broker, so preflight
cannot tell an https URL there apart from a working deployment. It now
warns for both schemes and fails https only for an address the Broker's
plain-HTTP socket takes in either mode.

---------

Co-authored-by: 易良 <[email protected]>
2026-10-25 20:29:23 +08:00
易良 94ccc02203 fix(cli): surface retry classification in headless mode (#15515)
* fix(cli): surface retry classification in headless mode

* fix(cli): cover continuation retries and disambiguate the full classification

* fix(cli): enforce the retry classification allowlist at the export boundary
2026-10-25 20:25:57 +08:00
易良 9c9088ee50 fix(ci): run credential-free release fanout on ecs runners (#15517) 2026-10-25 20:23:33 +08:00
33759ef518 fix(core): handle a failed queued memory extraction rejection (#15338)
* fix(core): handle a failed queued memory extraction rejection

A trailing auto-memory extraction starts from the finishing extraction's
finally block with `void`, so when it failed its rejection had no handler.
The task record and telemetry event already carried the failure, but the
rejection still reached the process handler: vitest counted it as a run
error that failed Core CI, and the interactive CLI's handler would report
CRITICAL and open the debug console.

Attach the same debug warning the two direct scheduleExtract callers use,
so the failure stays observable without escaping.

The lifecycle integration test now drains the manager before deleting its
temp directory. A failed or timed-out attempt no longer leaves a running
extraction writing into a removed tree, which is what produced the ENOENT
that surfaced after teardown.

Fixes #15269

* test(core): wait out every extraction before lifecycle teardown

A single drain() snapshots the in-flight set, so an extraction queued by
a finishing extraction's finally block registers after that snapshot and
still outlives the teardown. A failed attempt that stops before awaiting
its first extraction therefore deletes the tree under the queued run and
records it as failed with ENOENT.

Drain until no extract task is running or pending, which is the invariant
the teardown needs before it removes the project.

Fixes #15269

* test(core): pin queued-extract and teardown drain coverage (#15338)

A failed queued extraction has no caller, so its task record and telemetry
event are the only trace. Pass a config so the telemetry branch runs, and
assert both carriers rather than only the terminal status.

The lifecycle teardown wait loop was entered by no committed test. Add a
case that ends with a queued successor still gated, and assert extract work
is terminal and failure-free before fs.rm deletes the tree.

---------

Co-authored-by: 易良 <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
2026-10-25 20:20:02 +08:00
Shaojin Wen 67d4d3f1b2 fix(web-shell): keep UI language changes out of standalone workspace scope (#15521)
A standalone (no-workspace) session refuses `/language ui --project`, so
changing Language: UI from the settings panel's default Workspace tab failed
with "Project language settings are not available in this session."

Offer the UI language row only under User and open the panel there when the
active session cannot write workspace settings. Keep a defensive guard that
downgrades a workspace-scoped language change to `--global` for standalone
sessions.
2026-10-25 19:59:22 +08:00
jinyeandQwen-Coder 33e80fc1aa fix(runtime): unblock independent resident workspaces and tool admission (#15457)
* docs(runtime): revise resident Workspace delivery scope

Remove complete CSI auditing from the first-release prerequisites and retain bounded retirement with ownership preserved. Record platform-managed reclamation as separately designed and qualified work.

Co-authored-by: Qwen-Coder <[email protected]>

* docs(runtime): distinguish withdrawal and acceptance baselines

Keep the historical audit withdrawal baseline separate from the source pinned for S1 qualification.

Co-authored-by: Qwen-Coder <[email protected]>

* docs(runtime): record resident cold placement acceptance blocker

Co-authored-by: Qwen-Coder <[email protected]>

* docs(runtime): record resident public tool admission blocker

Co-authored-by: Qwen-Coder <[email protected]>

* fix(runtime): unblock independent resident workspaces and tool admission

Keep independent resident mount paths distinct from storage ownership and reject unsupported tools before dispatch while preserving public Plan approval and existing unknown outcomes.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(runtime): preserve hosted capability lookup compatibility

Limit resident capability discovery to file profiles, reuse validated warm metadata on legacy route absence, and allow failed queries to be retried. Preserve existing nonresident paths and fault-driver identity assertions.

Co-authored-by: Qwen-Coder <[email protected]>

* test(cli): drain pending hosted approval turns before teardown

Cancel and await the test-owned execution when approval observation or assertions fail, retaining the original happy-path awaits and timeout.

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-25 19:05:04 +08:00
6dfd4e4747 fix(runtime): deliver bounded provider images and PDFs (#13606)
* fix(runtime): deliver bounded provider images and PDFs

Use invocation-local media capabilities for actual file reads so
managed providers can deliver images and PDFs without a model or
inherited Omni uploads.

- Enforce inline media and complete result budgets before settlement
- Preserve accepted media across execute, status, and cancel responses
- Propagate PDF cancellation and wait for child cleanup
- Cover capability isolation, reply loss, and Broker restart recovery
- Document provider media limits and refresh dependency notices

* fix(runtime): repair media review validation gates

Keep provider media validation usable in standard CI while retaining
explicit activation of the full MySQL media scenario.

- Gate media integration on the existing bundle-ready opt-in
- Align PDF call assertions with the optional cancellation signal
- Keep the maximum-result fixture writable without changing its budget
- Explain the complete-result gate and restore baseline notices

* fix(runtime): preserve media diagnostics and clean up cancelled reads

Keep admitted media observations intact while retaining useful Hook and
PDF diagnostics. Bound fixture cleanup so cancelled reads and stalled
processes do not leave unused files or consume test resources.

- Fit Hook reasons and errors against the actual response budget
- Handle PDF ranges beyond document length and retain size errors
- Preserve read failures and propagate PDF fetch cancellation
- Add regression witnesses and instrument restarted media workers
- Bound MySQL commands and include media scratch paths in cleanup
- Record Computer Use comparisons and the typed browser facade plan

* fix(runtime): bound PDF cancellation and strengthen media probes

Prevent cancelled PDF reads from waiting forever for an unresponsive
child and make provider-media verification reflect the live runtime.

- Escalate cancelled PDF children and bound the remaining close wait
- Track the restarted provisioner and share fixture scratch prefixes
- Preserve MySQL initialization diagnostics during cleanup
- Skip redundant page probes and verify delivered ranges and statuses
- Update media design guidance for diagnostics and cancellation

* fix(runtime): tolerate the expected media probe reply loss

Keep lost-execute response loss from poisoning recovery evidence
while preserving fatal errors for all other requests.

- Consume and count one drop for the matching execute call
- Adapt media tests to the borrowed descriptor source
- Assert oversized descriptor media is refused before reading

* fix(runtime): verify media reply loss at the worker probe

The media driver checked a worker route that its broker client never
traverses, so the required loss counter always remained zero.

- Observe the actual Java probe drop instead of a proxy-only counter
- Allow broker recovery while preserving the lost-start assertions
- Verify the real drop, file replacement and single-use evidence

* fix(core): remove duplicate filesystem service import

The automatic main merge retained both copies of the same import and
failed the TypeScript build with TS2300.

- Keep one upstream import for the existing filesystem test fixtures

* fix(runtime): align media probe with the managed schema

Keep the provider media acceptance probe usable after the main schema
rollout replaces legacy tables and logical storage ownership keys.

- Read execution and runtime evidence from canonical managed tables
- Resolve physical ownership through the tenant storage alias
- Cover prepared, settled and released evidence on the fresh schema

* style(runtime): align media probes with Java checks

Keep the media integration gate compatible with the Java rules
introduced on main.

- Sort imports and remove the obsolete charset import
- Brace existing conditions and loops without changing behavior

* fix(runtime): adapt media validation to server migration

Keep provider media validation runnable after the Java server move
and the managed schema's epoch-millisecond timestamp conversion.

- Point the MySQL launcher at the migrated server POM
- Bind a numeric last-active time in the canonical schema fixture

* style(runtime): fix inherited MCP fault test checkstyle

Keep the module verification gate passing after integrating the new
upstream MCP fault test.

- Remove an unused CompletableFuture import
- Normalize blank lines without changing test logic or assertions

* test(runtime): align media probe with execution membership

Keep the media acceptance probe compatible with shared runtime
execution membership after integrating main.

- Check the original binding, generation and physical storage member
- Reject mismatched or exclusive members and leaked released membership

* docs(runtime): add the English provider media design and drop unrelated CUA docs

Translate the provider media design so the bilingual pair linked from the
Chinese document resolves.

Drop two CUA planning documents that were carried by the media commit but
describe unrelated browser work.

The Java/MySQL provider-media scenario passes locally against an isolated
MySQL instance: 12 cases, 24 model requests, one dropped start, every
reference dispatched once and released after broker restart.

* test(runtime): adapt the provider-media M4 driver to the broker client

main replaced the high-level broker provider with
ManagedRuntimeBrokerClient. Add a test-only adapter that exposes the
ManagedToolV2Client surface the M4 driver uses, backed by the new client,
and update the driver import and status narrowing.

Verified: integration typecheck, ESLint, and the Java/MySQL M4 scenario
(12 cases, 24 model requests, one dropped start, all references dispatched
once and released after Broker restart).

* style(tests): add the broker media adapter final newline

Restore Prettier compliance for the provider-media integration helper
so the formatting lane can pass.

---------

Co-authored-by: wangheyang <[email protected]>
Co-authored-by: wenshao <[email protected]>
2026-10-25 18:49:56 +08:00
易良 ccd463a835 perf(memory): retain unchanged legacy catalog snapshots (#15246)
* perf(memory): retain unchanged legacy catalog snapshots

Keep an unchanged legacy memory catalog at a stable history position so later requests can reuse the cached prefix. A changed catalog appends a full snapshot that supersedes earlier ones; resume, retry, compression and suppressed-memory requests handle the snapshots consistently.

Refs #15027

* fix(core): keep resumed token counts when legacy catalogs stay visible

* fix(core): keep catalog snapshots exact across replay and recovery

IDE context no longer merges into a marked catalog snapshot, so replay
parts keep the exact text deduplication compares. A turn that ends
without a usable usage report still charges a retained catalog, and an
output-recovery request strips stale snapshots before appending the
current catalog. Curated shallow tails filter marked parts like their
siblings, and the latest-snapshot scan is shared between live requests
and transcript replay.

Tests now delegate the client environment stubs to production, pin the
retained-catalog reads and side-query strips with immutable sentinels and
a catalog-active pass, make the telemetry-mirror assertions order
sensitive, and fail without the replay marking loop, the recovery
retain flag, the replayed-snapshot flag slot, or the snapshot-only
classification.

Design docs record the one-way token-count invalidation and the complete
per-package validation recipe.

Refs #15027

* fix(core): reuse the gate estimate for the zero-count output clamp

When lastPromptTokenCount is 0 and no manual plan-exit notice was consumed, the fallback ignores the conservative flag, so recomputing for the clamp repeats the same full shallow-history walk the gate already paid for. Reuse the gate estimate on that path, matching the pre-change behavior. Also add an aborted-turn witness that an uncharged retained catalog still feeds the next gate estimate with an authoritative count flag.

Refs #15027

* fix(memory): deduplicate recording type import after base merge

* test(memory): compare serialized history after prompt identity restore
2026-10-25 18:38:27 +08:00
6aa6438136 fix(managed-agent): guard lifecycle renewal scheduling during shutdown (#15208) (#15231)
* fix(managed-agent): guard lifecycle renewal scheduling during shutdown (#15208)

SessionLifecycleCoordinator.deliver scheduled its lease renewal after
stopRenewals had already shut the scheduler down. The rejected task
escaped on the shared executor thread during context shutdown, which is
the RejectedExecutionException the MySQL lane reported alongside a
concurrently running integration test. Treat a closed scheduler as
shutdown: return without scheduling and let the claim lease re-drive the
operation after restart.

* fix(managed-agent): log and pin abandoned shutdown deliveries

Log the operation and lease when a queued lifecycle delivery is
abandoned at shutdown, and make the regression test assert that the
delivery stops before any Harness call or durable write instead of
only checking that no exception escapes.

* fix(managed-agent): qualify the shutdown abandonment log and comment

SessionLifecycleCoordinator's shutdown guard told operators the claim's lease re-drives the abandoned operation unconditionally. The recovery scan reaches expired leases only when its first page has room under SCAN_LIMIT, so a sustained PENDING/BLOCKED backlog delays the re-drive past the lease. Say so in the comment and the WARN; no behavior change.

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: qwen-code-autofix <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-10-25 18:21:17 +08:00
Shaojin Wenandverify 3eff5f7793 feat(web-shell): create workspaces from a Git URL (#15361)
* feat(web-shell): create workspaces from a Git URL

The Add-workspace dialog could only register an existing directory or
create an empty scratch directory, so starting from a repository meant
leaving the browser, cloning on the daemon host, and registering the
path afterwards.

Add a Git source that starts an asynchronous clone job, reports progress,
and supports cancellation. A successful clone is registered through the
existing workspace route, so capacity, nesting, persistence, and the
daemon's trust policy stay in one place.

* fix(cli): classify git_workspace_clone in the capability parity matrix

The parity matrix landed on main after this branch, and every capability
tag needs a row. Managed workspace creation only creates empty
directories, so record the clone surface as an explicit Managed gap
rather than claiming parity.

* fix(cli): reserve active clone targets before overlapping work

An outer clone's failure/cancel cleanup removes its target recursively,
so a nested clone or workspace registration must not start underneath a
running job. Reserve the canonical target for the job lifetime, reject
overlapping clone and registration paths, and release the reservation
only after cleanup settles.

Also resolve the success path before publishing `succeeded`, so a reader
never observes a terminal snapshot without the `cwd` it registers.

* fix(web-shell): keep clone polling recoverable

A failed poll left the last known status running, so the panel offered
Cancel instead of the recheck that can recover the existing job. Prefer
the unknown-state action.

Reset the unmounted guard in the effect setup: StrictMode's development
replay otherwise leaves it set and discards every later poll, including a
successful clone.

* feat(web-shell): reuse the directory path as the clone parent

When the source switches to Git, treat the directory path as the clone
parent and append the repository name from the URL. A path whose basename
already matches stays as-is, and a manually edited or browsed target is
never overwritten by later URL edits.

* feat(web-shell): suggest folders for the clone target

Headless daemons do not provide a native directory picker, so the clone
target had to be typed in full. Reuse the directory suggestion list:
selecting an existing folder uses it as the parent and appends the
repository name, while a manually edited target still wins.

* fix(cli): keep clone progress monotonic

git restarts the percentage for some clone phases (remote counting,
then receiving), so the Web Shell progress bar could jump backwards.
Clamp the reported percent to the running maximum; the status message
still follows the current git line.

Also pin the clone admission guards left unpinned by review: reserved
roots, unknown request keys, promotion of a workspace under an active
clone target, file URLs that name a host, percentages above 100, and
the dialog fallback when the daemon capability disappears.

* fix(cli): reserve the clone target before claiming it

The claim created the target with mkdir, then awaited the existence check
and the final realpath, and only reserved the canonical path afterwards.
A concurrent ordinary registration could admit the freshly created
directory in that window and then lose it to the clone's cancel/failure
cleanup.

Reserve the canonical target inside claimCloneTarget before mkdir, share
the reservation with registration, and release it on every claim failure
and on post-claim admission failures. Add a regression test that overlaps
the registration with the blocked claim and a unit test that asserts the
reservation exists before the directory does.

Also update the serve integration capability envelope for the
host-conditional git_workspace_clone tag, mirroring the daemon's probe.

* fix(cli): register git_workspace_clone in serve capability guards

Test (ubuntu) failed five capability-registry guards because the new
git_workspace_clone tag was not registered alongside the existing
conditional features:

- document it in the qwen serve protocol table;
- allow the clone spawner's process-environment copy and SystemRoot
  lookup in the serve process.env guard;
- add it to EXPECTED_REGISTERED_FEATURES and the conditional-feature
  drift-insurance branch.

Verified: capabilities-docs-contract, process-env-guard and server
suite 1399/1399; typecheck, ESLint, Prettier clean.

* fix(cli): keep clone targets away from reserved and Live paths

A clone target that contains the reserved Conversations root was accepted
when that root did not exist yet: creating the target made the root's
parent part of the clone, and the Live runtime could then be published
inside it. Because the Live exemption skipped the "canonical inside
target" direction for active clone targets, cancelling or failing the
clone deleted the registered Conversations workspace.

Reject clone targets that contain a reserved root, even before the root
exists, and apply strict two-way nesting to active clone targets in the
owned-runtime admission regardless of provenance.

Verified: workspace-git-clone + workspace-management 196/196;
typecheck, ESLint, Prettier clean.

* fix(cli): resolve reserved roots through existing ancestors

The reserved-root containment check fell back to the unresolved spelling
when the Conversations root did not exist yet. A symlinked ancestor such
as ~/Documents then made the canonical clone target and the lexical
reserved path disagree: the clone was accepted, the Live runtime created
the root inside it, and cancelling the clone left ConversationWorkspace
with a cached identity for a directory that no longer existed.

Resolve each path through its nearest existing ancestor and append the
missing suffix before comparing, so a clone target containing a missing
reserved root is rejected before mkdir even when ancestors are symlinks.

Verified: workspace-git-clone + workspace-management 197/197;
typecheck, ESLint, Prettier clean.

* fix(cli): cover case aliases and dangling links in reserved roots

The reserved-root guard compared the reconstructed missing suffix with
case-sensitive isWithinRoot, so a case-variant ancestor such as
`Documents/qwen code` did not match `Documents/Qwen Code/Conversations`
on a case-insensitive filesystem. It also walked past a dangling symlink
ancestor, so a clone target that was the link's missing destination
could later make the link valid inside the clone's cleanup scope.

Compare canonical paths case-insensitively after resolving through the
nearest existing ancestor, and follow dangling symlinks while
reconstructing the missing suffix. Add regressions for both aliases.

Verified: workspace-git-clone + workspace-management 199/199;
typecheck, ESLint, Prettier clean.

* fix(cli): harden git clone credentials, config, and admission

- Strip URL credentials before git's argv and supply them through a
  command-line credential helper backed by the child environment, so the
  secret no longer appears in /proc/<pid>/cmdline or .git/config.
- Disable global/system git config for the clone child so a repository's
  .gitattributes cannot run host-configured filter commands during checkout.
- Keep the first fatal/error stderr line for clone failures instead of the
  trailing SSH boilerplate.
- Only treat Receiving objects/Resolving deltas as progress phases, so
  remote counting cannot pin the bar at 100.
- Strip OSC/C1 and all other control characters from clone messages; add
  the missing Windows taskkill 'error' listener.
- Require operator authority for clone cancellation; reject clone targets
  inside the managed scratch root or equal to a registered/in-flight
  workspace; keep case-insensitive containment scoped to clone targets.

Verified: workspace-git-clone + workspace-management 205/205;
typecheck, ESLint, Prettier clean.

* fix(web-shell): gate the clone flow during submit and IME input

- Disable the post-clone Back button while registration is in flight and
  guard backToForm, so it cannot reset a submitting flag owned by onAdd.
- Ignore clone-target key handling while an IME composition is active.
- Dismiss the clone-target suggestion list when the git form is submitted,
  so its Escape interceptor cannot stay armed over the progress view.

Verified: AddWorkspaceDialog 57/57; Prettier and ESLint clean.

* docs: align git clone guarantees with credential and config isolation

- Document the `{ kind: "git" }` form and its 202 job response in the
  `POST /workspaces` reference.
- State that URL credentials are moved out of git argv and the stored remote
  URL, and that global/system git config is disabled for the clone child so
  committed filters cannot run host commands during checkout.
- Mirror both changes in the Chinese design document.

* fix(cli): use bracket access for clone credential env

The strict index signature on NodeJS.ProcessEnv rejects dot access to
QWEN_GIT_USERNAME/QWEN_GIT_PASSWORD; all CI jobs that build the CLI
failed at tsc with TS4111 before running any test.

* fix(web-shell): cancel the running clone from the left action

- Route the progress panel's left Cancel through cancelClone while the job
  is running, so it stops the daemon job instead of only closing the dialog.
- Clear the pending poll timer at pollCloneJob entry and before cancel, and
  guard registration per job id, so a terminal snapshot cannot register the
  same clone twice.

Verified: AddWorkspaceDialog 57/57; Prettier and ESLint clean.

* fix(web-shell): harden git clone env and clone-job dialog states

Pin LC_ALL/LANG for the clone child and sweep host Git config channels (GIT_CONFIG_*, GIT_ASKPASS, GIT_SSH*, GIT_PROXY_COMMAND) so a localized git still reports parseable progress and a browser-chosen URL cannot borrow host credentials. Decode URL credentials defensively, reject control characters, and print the inline credential helper with printf so dash does not mangle tokens.

In the add-workspace dialog, tear the clone state down when the capability disappears, report a pruned clone job instead of offering recheck forever, honour a cancel that loses the race with a finished clone, and close instead of cancelling an unconfirmed job. Correct the host-authentication prose in the design docs, README and URL hint to match what the isolated clone child actually supports.

* fix(cli): resolve relative symlinks physically and keep cancel intent

- Resolve a relative symlink target from its physical parent before appending
  the missing suffix, so a dangling relative Documents link cannot hide the
  reserved Conversations root from clone admission.
- Keep the per-job cancellation intent across overlapping poll responses, so
  a second succeeded poll cannot auto-register after Cancel. Clear it only on
  an explicit Back/new submission.

Verified: route + clone 209/209; AddWorkspaceDialog 61/61; typecheck with a
6 GB heap; Prettier and ESLint clean.

---------

Co-authored-by: verify <verify@local>
2026-10-25 10:16:38 +00:00
jinye 2a245baff5 fix(cli): Hide unavailable Managed background tools (#15497) 2026-10-25 10:07:21 +00:00
Shaojin Wen 1df5031e70 perf(ci): overlap web-shell and standalone tests with the CLI suite (#15438)
* perf(ci): overlap web-shell and standalone tests with the CLI suite

The Linux Test job ran every workspace suite, the standalone runtime
regression and scripts/tests one after another. The CLI suite keeps its
workers busy for 13-20 minutes, while web-shell's suite spends most of its
run on App.test.tsx alone and the standalone regression starts one
runtime process at a time, so those two added their mostly idle minutes
straight to the job.

Run them in a side lane beside the other workspaces instead.
.github/scripts/ci/run-unit-test-lanes.sh starts web-shell and the
standalone regression in the background, runs every other workspace's
test:ci (listed with npm pkg get, in npm's workspace order) and then
scripts/tests, and replays the side lane's log after the main lane so
the two never interleave. The step runs the script under the same
setpriv wrapper, so every test command still runs without the DAC
override capabilities.

test:scripts now reaches its Vitest run through a new test:scripts:unit
script with a trailing --, so the main lane can run it without the
standalone regression while test:ci, test:ci:workspaces and test:scripts
run what they ran before.

* fix(ci): name the cause when the lane script cannot start a lane

- Fail with an ::error:: line when mktemp cannot create the side lane's
  log file, instead of running the main lane alone and dying in tail.
- Check the exit status of `npm pkg get` and of the parser. A failing
  `npm pkg get --json` prints an error object, which used to parse as an
  empty listing and report "Found no workspaces".
- Note in the header that the replay puts side-lane failures after the
  main lane's in main-failure-signature.mjs's title and search markers.
- Lane test: clear the caller's NODE_OPTIONS, assert the main lane's
  output is present before checking its order, and cover the failed
  listing and the missing temp dir.
- Cross-check npm's workspace resolution against the selector in
  scripts/workspaces.js.

* fix(ci): halve web-shell's worker cap beside the main lane

Each Vitest run applies VITEST_MAX_FORKS / VITEST_MAX_THREADS on its own,
so running two lanes at once doubled the step's worker cap, and on the
hosted runners, where no cap is set, put two all-CPUs pools on 4 vCPU.
The lane script now runs web-shell at half the cap, at least 1, and at 2
where the cap is unset; the main lane keeps the full cap.

- Refuse a workspace name with whitespace in it: the main lane's
  arguments are read one per line, so a line break would split a name
  into other arguments, flags among them.
- Lane test: web-shell and the main lane's suites now wait for each
  other, so a side lane serialized before or after the main lane fails;
  record each call's worker caps; run the script against npm's real
  listing and compare its main lane with the selector the release pins
  use; cover a name with a line break in it.
- ci.yml: the standalone runtime cache comment names the lane script's
  side lane, not test:scripts, as what runs the regression in this job.
- ci-variables.md: say the cap applies per Vitest run and what the
  two-lane step runs at its peak.
- release-workflow.test.js: follow test:scripts through
  test:scripts:unit to vitest; #15102 pinned the shape before the split.
2026-10-25 10:05:56 +00:00
Shaojin Wen 4b32800459 feat(cli): list /autofix-pr watches and resume them in a later session (#15510)
* feat(autofix-pr): list watches and resume them in a later session

A watch ends with the session that ran it and its scheduled task expires
after 7 days, and getting it back meant typing `/autofix-pr <n>` again and
confirming a take-over, PR by PR, with the interval, `--no-reply` and
instructions remembered by the user. `status` also had nothing to read
but the state files' raw fields.

- `qwen autofix-pr status [<pr>]` lists every watch of the clone from its
  files, without a lock or a GitHub call: head, pushes, owning session,
  the last gate check (the gate now touches its cursor on every run) and
  round, whether the gate saw the PR end, the recorded task and what was
  left for a person.
- `qwen autofix-pr schedule <pr>` records the watch's task in the state
  file and prints its cron, gate and prompt for `cron_create`, so the
  skill no longer composes them; `--again` prints the recorded task for
  the session that took the watch over.
- The reply plan takes `needsHuman` lines, kept per head (newest 20) for
  `status`.
- The skill gains `/autofix-pr resume [<pr>]`: one confirmation, then a
  take-over, one round and the recorded task for each watch this session
  does not run.

* fix(autofix-pr): keep resumed watches whole and their old tasks gone

- prepare keeps the recorded task and needsHuman when it rebuilds a
  removed worktree, so resume can schedule it again.
- A round woken by a task made for a session that no longer owns the
  watch deletes that task (watched_elsewhere now names gateSession); the
  start and resume delete such tasks before the round, not after a
  schedule that may be refused.
- status reads the gate cursor only for the watch's repository, keeps a
  watch whose cursor cannot be read, and reads only the state files the
  helpers name, each of its own PR.
- cleanup --take-over lets stop end a watch another session left behind
  while keeping its work, and a cleanup that keeps the worktree drops the
  recorded task so resume does not discard the kept work.
- resume goes through a round for a watch the gate saw end, rather than
  cleaning it up unconfirmed, and leaves out a watch another session
  checked recently unless the PR is named.
- The start records its task before the round and schedules it only if
  the round did not stop the watch; session ids must start with a letter
  or digit, control characters in instructions are folded, and a
  recorded task is held to one line of 2,000 characters.

* fix(autofix-pr): give each watch one task and say when another session runs it

- schedule mints a task id each time (the gate carries --task); a gate
  whose id the state no longer records — a task a later start or resume
  replaced, or a copy in a second process of the same session — answers
  watched_elsewhere, and a --session gate whose watch cleanup removed
  answers watch_ended, so the round deletes exactly that task. The start
  schedules with --again after its round, which refuses a watch the round
  ended, and schedule refuses a PR whose author cannot push.
- status says whether a gate checked within two intervals
  (checkedRecently, with now), what the worktree holds (worktreeWork), and
  reads only regular files; resume and take-over confirmations quote both.
- prepare --take-over on a removed worktree moves the watch only after the
  PR passed its checks; cleanup drops the recorded task only while it still
  owns the watch; stop takes over every PR it ends.
- Instructions and needsHuman lines drop format characters.

* fix(autofix-pr): let only a watch's own task run its rounds

- The task's prompt carries its id (tick ... --task <id>) and a round's
  first sync checks it: a fire queued before a later schedule replaced the
  task is refused, and the round deletes it. Resume runs schedule --again
  before its round, so a second process of the session stops there too.
- A cleanup that keeps the worktree marks the watch ended: the round
  helpers refuse it, its gate answers watch_ended, and only a start resumes
  it (prepare reports wasEnded).
- The gate counts a task with no id as stale once the watch records one,
  answers watch_ended to a task of a watch that records none, and refuses
  --task without --session.
- The watch's own gate marks its checks in autofix-pr-<n>-gate.checked,
  which cleanup removes; a gate with no session leaves it, and status
  takes nothing from a cursor older than the watch (preparedAt).
- The state accepts only the four task fields, and schedule builds the
  task from them field by field.
- A stop the user did not name a PR for takes no watch from another
  session.

* fix(autofix-pr): keep a recreated watch on cancel and check the task at every helper

- prepare says recreated when it only made a removed worktree again, so a
  cancelled start, or the end of a --once round, cleans up only a watch it
  made; a --once round that resumed a stopped watch ends it again, and a
  stopped watch is resumed only after the question --yes cannot skip.
- snapshot, ci-log, push, reply and rerun take the round's --task too, so a
  round already past sync when a later schedule replaced its task stops at
  its next helper. Taking over a round that is still running stays a race
  for its fix agent, which calls no helper: the questions say so.
- An ended watch says so before ownership is checked, and another
  session's watch is offered back with resume only when it records a task.
- The state's watch ignores fields it does not know, which never reach the
  task; status runs git without optional locks, names the file and the way
  out for what it cannot read; the start deletes the PR's other tasks
  before its round.

* fix(autofix-pr): schedule a start's task from what was printed before its round

- The start and resume make their task from what schedule printed before
  the round — the round runs under that task id — once status shows the
  watch still records it, so nothing the fix agent writes to the state
  file reaches the task or its prompt.
- The state is read with the known fields of watch, ended and needsHuman
  only; a rebuild of a stopped watch's removed worktree reports wasEnded;
  a cleanup that keeps a worktree removes the gate's check mark.
- The instructions file is read only if it is a regular file, before the
  lock; a refused task names which task to delete and which to keep.
- The skill: every round helper's first four refusals are handled alike;
  a take-over warns of another session only when no task of the PR is
  listed here; a resume that stops a watch takes it over; a refused
  schedule after a take-over leaves the watch to resume.

* fix(autofix-pr): resume every watch's task before any round, and ask GitHub who may push

- Resume takes over and schedules every watch it resumes before any round
  runs — a round's fix agent works beside the other watches' state files —
  leaves out one whose printed options are not those it asked about, and
  runs each round with the options schedule printed.
- prepare asks GitHub again on a resume whether the PR's author can push
  instead of reading it back from the state file, and schedule refuses any
  state but authorCanPush: true.
- readState reads only a regular file, so --force can discard a FIFO
  planted as the state; cleanup that finds the watch taken over during its
  check refuses instead of reporting it ended.
- The skill: stop reports the needsHuman items cleanup removes; status
  sorts ended and --once watches first; --yes does not skip the question
  where another process may be running a round; the start's and resume's
  status check also requires thisSession; a refused cron_create says to
  resume.

* fix(autofix-pr): ask GitHub before moving a watch, and read only regular files

- Only a 404 from the permission API means the author cannot push; any
  other failure is a refusal that changes nothing, so a rate limit no
  longer ends a running watch. A resume asks GitHub before a take-over is
  written, so a failure leaves the watch with the session that has it.
- A resume derives the remote and the push target from the clone's
  remotes again, and refuses a repository the clone does not serve.
- The gate cursor, the history and the reply cache are read only when
  they are regular files, as the state is (a link to nothing included),
  and cleanup's sweep removes a directory planted under a watch's names.
- cleanup checks again under the lock that the watch is still this
  session's before it removes anything, and keeps the latest history.
- The skill: a PR whose author lost write access has its tasks deleted and
  its watch ended after a --once round; a refused instructions write ends
  the start; the start's last line reports a watch only when its task
  exists.

* fix(autofix-pr): write a watch's files without following links, and ask who may push before making anything

- Every write of a watch's files (state, cursor, history, snapshot, reply
  cache, CI log) replaces a link instead of following it, and reply reads
  the round's snapshot only if it is a regular file.
- A new watch asks whether the author can push before it fetches or makes
  anything, so a failed ask leaves no worktree, branch or state; a resume
  refuses a PR whose head repository is gone, and reads and clears ended
  under the lock that writes the rest.
- cleanup removes a watch under the lock after checking ownership again;
  a directory planted at the history is replaced, and --force clears a
  cursor that is no regular file.
- A state that is no regular file names cleanup --force; a cancelled start
  of a PR whose author cannot push cleans up too.

* fix(autofix-pr): keep human decisions and a failed take-over intact

- reply: record what a round left for a person before posting anything.
  A reply that reached the PR marks its comments handled at once, so after
  a later POST failed the next round never listed them again.
- cleanup --take-over: move the watch only as it ends, under the lock. A
  stop that failed before then left the watch owned by no running session,
  and its owner's gate stopped it.
- One predicate for gh's 404, read from its stderr.
- Skip the FIFO tests on Windows.

* docs(autofix-pr): say a daemon session refuses a watch it cannot run, on a resume too
2026-10-25 17:58:31 +08:00
jinye a4ce90270e feat(managed): add durable mid-turn user message queue (#15449) 2026-10-25 17:53:56 +08:00
a4331340ad fix(ci): skip main-failure issues for post-merge reruns (#15239) (#15250)
* fix(ci): skip main-failure issues for post-merge reruns (#15239)

A rerun re-executes the PR's pre-merge workflow. When it starts after the
PR merged, its failure describes a head that main has already replaced,
and the post-merge push run owns that signal. Compare the attempt's start
time with the merge time and skip filing for the stale head.

Run 39768204855's attempt 2 started after PR #15094 merged, hit the
pre-#15128 suite-wide host-lock hold, and filed #15239.

* fix(ci): discriminate post-merge reruns by attempt (#15239)

The clock proxy suppressed fresh manual dispatches and post-merge
approval runs while missing reruns dispatched before the merge. Use
run_attempt, which states rerun identity directly: attempt 1 still
files, later attempts on a merged PR keep the comment but open no
issue. Announce the empty-attempt fail-open path.

Preserve an existing tracking-issue link when a suppressed attempt
updates the PR comment, and sync the design docs and the contract
comments with the narrower scope.

* fix(ci): pin rerun guard scope and tracking-link recovery (#15239)

Drop the pre-loop body write that no publish path could read, and cover the
publish-retry truncate with a row that fails the first publish. Pin the
writer's tracking line against the recovery grep's anchored pattern, and pin
that the attempt guard never suppresses a main run. Scope the design doc's
exclusivity sentence to the infrastructure path in both languages.

The workflow size ratchet records the file's new size; the growth is the
attempt guard and tracking-link recovery this PR adds.

Co-authored-by: Qwen-Coder <[email protected]>

* test(ci): pin tracking-link precedence for PR failure comment (#15239)

Co-authored-by: Qwen-Coder <[email protected]>

* test(ci): pin tracking-link wiring and POST coverage (#15239)

* fix(ci): file dispatch reruns on merged PR branches (#15239)

* test(ci): pin merged-PR rerun event and lookup-failure publish guard (#15239)

Co-authored-by: Qwen-Coder <[email protected]>

* test(ci): pin approval-run suppression and merged-PR comment guard (#15239)

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-10-25 09:49:49 +00:00
ba051f804a fix(ci): run the sdk-java suite before taking the host lock (#15254)
* fix(ci): run the sdk-java suite before taking the host lock (#15242)

Run 39771949573's fast-tier job spent both attempts' full 5400 s lock
budget waiting: the full-tier Java 21 leg of run 39754414693 ran for 89
minutes on the same host, and the fast job failed five seconds after that
leg ended. The MariaDB lane's #15128 fix scoped the lock to its cache
write-back, but the test job still held the host lock across the SDK and
Managed Agent Maven suites.

Seed the test job's Maven repository from the host cache, run its suite
from that job-local copy, and take the ticketed host lock only for the
cache write-back. The write-back children close fd 9 so a cancelled job's
survivor cannot retain the lock, and the workflow pins move with the new
order.

* fix(ci): keep the SDK Java gate green when the write-back lock expires (#15242)

The write-back lock acquisition could redden a job whose suite had already passed (CI run 39814549427). Treat it like the best-effort copy it guards: warn and skip on timeout in both the helper and fallback arms, while leaving the helper's pinned lock-not-acquired signature alone.

Tighten the workflow pins: assert the region below the lock instead of first-occurrence Maven indices, pin both write-back copies byte-equal, bind the job-local MAVEN_ARGS to its step, and include the write-back bound in the test job's ceiling model. Restate the hold-length derivations and record the concurrency blast radius in both design languages.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): keep the lock helper outside this PR's footprint (#15242)

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): describe the sdk-java 89-minute leg as the leg, not the suite (#15242)

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): keep tolerated sdk-java lock timeouts out of rerun classification (#15242)

The warn-and-skip downgrade left the helper's `lock not acquired within`
annotation on a step that now exits 0, so a later genuine failure in the
same job could be classified as infrastructure. Both call sites rewrite
the timeout line before it reaches the log, and probe flock plus the cache
directory first: bash suppresses errexit for a function on the left of
`||`, so without the probes a setup fault spun out the full wait green.

The skip test now plants a publishable delta, asserts nothing was
published, checks the captured annotations against the live
INFRA_FAILURE_SIGNATURES list, and adds a setup-fault arm. The workflow
size baseline is re-measured, and the design docs' classifier, promotion,
and budget claims are corrected in both languages.

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Qwen Autofix <[email protected]>
2026-10-25 09:45:34 +00:00
Shaojin Wen 1c9cdd234f feat(verify-pr): declare an argument hint for the command prompt (#15463)
* feat(verify-pr): declare an argument hint for the command prompt

Typing `/verify-pr` showed no parameter hint, unlike `/review`, because the
skill's frontmatter carried only `name` and `description`. The TUI renders
`argument-hint` beside the command name and `/review` relies on exactly that
field, so `/verify-pr` looked like it took no arguments at all.

The hint lists the PR number and every flag the skill accepts — `--publish`,
`--watch`, `--repo`, `--assets-repo`, `--assets-branch` — and a scripts
assertion pins it, so dropping a flag from the hint reddens the test.

* test(verify-pr): pin the parsed argument hint instead of its raw line

Assert the parsed frontmatter value so a requoted plain scalar stays green while a dropped value placeholder goes red, and record in the skill's environment contract that .qwen/** is the base commit's copy in the verify lane.
2026-10-25 16:55:21 +08:00
易良 5aeee2ceec fix(release): authenticate managed OSS preflight (#15504) 2026-10-25 16:51:21 +08:00
易良 cc49daa282 fix(ci): preserve independent validation for main push SHAs (#15508) 2026-10-25 16:51:04 +08:00
易良andShaojin Wen b423ed9042 feat(runtime-broker): add HTTP-backed external scratch workers (#15261)
* feat(runtime-broker): add external scratch HTTP provisioner

* docs(runtime-broker): format external provider design and state tool-consumer gap

* docs(runtime-broker): document external scratch provider deployment

* docs(runtime-broker): note external placement capacity bound

* fix(runtime-broker): free external retry slot on permanent outcome

* docs(runtime-broker): reconcile external provider verification evidence

* docs(runtime-broker): record exact-head local Java evidence

* test(runtime-broker): exercise external provider with real worker

Cover lost provision response, original worker adoption, UNKNOWN recovery and file tools in the existing fault gate. Runtime verification pending CI; no local Maven run within the exhausted shared budget.

* docs(runtime-broker): describe real-worker gate scope

* test(managed-agent): update duration field inventory

* test(managed-agent): re-acquire the Session after Broker recreation

Session liveness is per Broker process by contract (requireSession
checks the process-local map and rejects with 'Runtime Session is not
active in this Broker process'). The restored Broker must adopt the
worker and then the caller re-acquires the Session; the repository
dedupe reuses the READY record. The fault gate now mirrors that client
flow before driving executions on the adopted worker.

* fix(cli): drop the removed V2 tool lists from the parity test (#15362)

The parity matrix landed one commit before the shell-lifecycle unification,
which deleted HOSTED_WORKSPACE_FILE_TOOLS_V2 and HOSTED_WORKSPACE_SHELL_TOOLS_V2
and began adding glob inline when search is on. The test kept importing both,
so tsc --build failed at the merge-base and every PR's prepare step failed
before a single test ran.

Those two lists were the base lists plus glob, so dropping them removes no
tool name the matrix cites: its evidence tokens name edit, monitor,
run_shell_command and write_file only.

(cherry picked from commit 43d0d50e2c)

* test(cli): flush React updates between native attachment checks

* docs(runtime-broker): clarify restart acquisition and caller evidence

* fix(runtime-broker): keep external capacity rejection retryable

* fix(managed-agent): preserve external runtime retry ownership

* fix(runtime-broker): harden external scratch recovery

* fix(managed-agent): pin external runtime request bounds

* refactor(runtime-broker): trim external scratch scaffolding

* fix(runtime-broker): preserve external admission and confirmation ownership

* fix(runtime-broker): retain placement during reconciliation

---------

Co-authored-by: Shaojin Wen <[email protected]>
2026-10-25 16:50:45 +08:00
a5cf7aaa4d test(cli): budget the hosted approval wait for loaded runners (#15093) (#15101)
* test(cli): budget the hosted approval wait for loaded runners (#15093)

Co-authored-by: Qwen-Coder <[email protected]>

* test(cli): keep the hosted approval budget under the per-test ceiling (#15093)

Co-authored-by: Qwen-Coder <[email protected]>

* test(cli): pin the requested() timeout wiring to the slow-host policy (#15093)

Co-authored-by: Qwen-Coder <[email protected]>

* test(cli): pin the requested() wait at its use site (#15093)

Co-authored-by: Qwen-Coder <[email protected]>

* test(cli): keep malformed-Unicode test names JUnit-safe (#15093)

The parametrized malformed-Unicode cases interpolate lone surrogates into the test name. Vitest's JUnit reporter strips a lone low surrogate together with the preceding character, which is the semicolon of an &apos; entity, so packages/cli/junit.xml becomes invalid and the CI report publisher fails.

Label each value for the title and keep the raw value in the test body.

Co-authored-by: Qwen-Coder <[email protected]>

* test(cli): pin the hosted approval wait to the slow-host policy (#15093)

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: 易良 <[email protected]>
2026-10-25 16:42:22 +08:00
易良 10c036b655 fix(ci): keep the incremental lint history fetch blobless and visible (#15511)
Lint & Static can already hold a blobless merge base: Report test growth
fetches it with --filter=blob:none. A later plain depth-100 fetch then
fails resolving thin-pack deltas against blobs this checkout does not
have (fatal: missing blob object + remote did not send all necessary
objects), and continue-on-error turns that into a silent full scan:
4 of 8 sampled PR runs after #15381 merged, with 5 of 8 falling back to
full lint in total.

Fetch blobless too, and drop the recorded filter so later fetches in the
checkout stay ordinary. lint-scope reads only commits and trees.

Announce a history fallback as ::warning:: plus a step-summary line on
workflow runs, deduplicated across the seven lint.js invocations, so the
optimization can no longer stop working without a trace.
2026-10-25 16:31:34 +08:00
Kun De 07c1c2b30e fix(web-shell): simplify status when loading phrases are hidden (#15423) 2026-10-25 16:28:27 +08:00
qqqysandqqqys 58a4c1b9cc feat(generated-ui): continue analysis with reviewed parameters (#15333)
* feat(generated-ui): continue analysis with reviewed parameters

* test(generated-ui): reveal outer frames before continuation clicks

* fix(generated-ui): keep continuation feedback accurate when nothing was sent

A capture failure no longer reports that a sent request may still be
accepted, and the unconfirmed message now covers cancelled or rejected
reviews, which the host reports the same way as an unknown outcome.

---------

Co-authored-by: qqqys <[email protected]>
2026-10-25 16:12:38 +08:00
Shaojin Wen abb7fcf020 fix(cli): refuse an autofix watch a daemon cannot run (#15506)
* fix(cli): refuse an autofix watch a daemon cannot run

A daemon session that started /autofix-pr from a checkout other than the
PR's clone prepared its worktree in the clone's main worktree, outside the
session's own working directory. The shell guard then refused every command
in that worktree, so the round could not test, commit or push anything — and
the refusal named only a path, which sent the fix looking for a way around
the boundary instead of at it.

prepare now refuses that start in a daemon session, before anything is made
under .qwen/tmp, and names the directory the session has to contain. The
guard's refusal also says which roots it accepts, so a caller does not retry
the same command in a fresh session and call it a fix.

The skill says a refused shell command stops the round: a worktree nothing
can run in cannot be verified, committed or pushed, and no sub-session
should be spawned to run it one command at a time.

Tested: packages/core SKILL.test.ts (5), packages/cli prepare.test.ts (39),
packages/cli daemon-git-worktree-guard.test.ts (464 passed, 4 skipped),
eslint, prettier and a full typecheck.

* fix(cli): name the shell guard boundary, not the caller

The refusal added for a worktree outside the session's directory called the
affected caller a sub-agent. A session whose own effective directory moved
outside the one the daemon tracks is refused the same way, so the message
speaks about the directory rather than the caller.

The user guide had the containment backwards: the clone has to sit inside
the session's working directory, not the other way round. The prepare
refusal now names the clone root to open the session at instead of
describing the condition abstractly, so the sentence tells the reader where
to go.

The guard suite asserts the boundary hint, so a later edit cannot drop it
without a failure.

* fix(cli): refuse a daemon resume it cannot run too

The refusal only covered a fresh start. A resume reports the worktree
already there and returned before the check, so a session that resumed, or
took the watch over, in a sibling checkout was still handed a worktree the
daemon refuses every command in — the same dead end, one start path later.

Both refusals now run before the state is read, so a resume meets them as
well, and a new case prepares in the clone, adds a linked worktree, and
asserts that the resume from there is refused with the state file left
untouched.

The refusal says to prepare from the clone root, which is the fix even when
only the helper's working directory was narrower than the session's.
2026-10-25 16:01:23 +08:00
e84e0f3dac test(cli): budget hosted approval waits for slow runners (#15176) (#15183)
* test(cli): budget hosted approval waits for slow runners (#15176)

The hosted approval tests wait through a serial chain of fsync-backed
publishes and journal commits. On the shared post-merge CI pool that chain
can outlast the fixed 10s wait, so the continuation and inline-limit suites
failed with phase "before_model" instead of "await_action".

Pick the wait and per-test budgets through the existing slow-host policy:
fast hosts keep their current ceilings, slow hosts get 45s under the 60s
per-test ceiling, so a genuine stall still fails on the assertion.

* test(cli): pin non-ecs RUNNER_NAME fast budget (#15176)

Co-authored-by: Qwen-Coder <[email protected]>

* test(cli): pin the saturated-host equality boundary (#15176)

* test(cli): keep slow-host timeout headroom for hosted approvals (#15176)

* test(cli): budget hosted harness settle waits on slow hosts (#15176)

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen Autofix <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-10-25 07:53:46 +00:00
易良 b5958d300b feat(managed-agent): pool the automation scanner's rows and lease from the claim time (#15442)
* feat(managed-agent): pool the automation scanner's rows and lease from the claim time

A scan tick worked every armed definition on the scheduler's only
thread, so one gated fire_run holding the Harness relay (probe up to
20 s inside 30 s, plus up to 120 s of Session load) delayed every
definition on the node; measured gate runs of 10-13 s cap one node at
about four one-minute PR watches (#15418).

Work the tick's candidate rows on a bounded pool
(automation.scanner-threads, default 4), one task per row, claimed
inside the task, and join them before the tick returns so fixedDelay
still never overlaps. The lease now runs from the claim time, not the
tick's start: a task can start halfway through a lease's lifetime, and
a tick-start lease would be born part-expired and stealable by another
node. A dedicated automationScannerExecutor bean (never the one-thread
managedAutomationScheduler, which the tick parks on while joining)
finishes in-flight rows at shutdown without interrupting them.

The gate design's second route — answering a gated fire asynchronously
— stays open as a follow-up; it needs a wire change the funnel does
not carry today.

Refs #15418

* refactor(managed-agent): answer review nits on the scanner row pool

The DIRECT executor owns no threads, so it no longer simulates a
shutdown state: the lifecycle surface is inert and documented as such.
The design doc's Validation section now lists all eight new unit
witnesses, including the two the review fixes added.

Refs #15418

* test(managed-agent): pin the accepted same-Session fan-out and the tick join

The #15442 verify lane (verdict findings, 43/43 assertions) measured
three gaps; two are closed here and one is documented:

- Two definitions sharing one Session are admitted concurrently and
  both fire: across definitions the admission guards only on public
  Turns, and automation fires leave no ma_turn row, so the serial
  baseline behaved the same in production; the runs serialize in the
  Harness funnel. Accepted and pinned by the new shared-Session test,
  and recorded in the design's residual ceilings (finding 1).
- aTickJoinsEveryRowBeforeItReportsOneFailure now runs on a real
  one-thread pool with the second row's claim in flight when the first
  one throws; under the direct executor a fail-fast break in the join
  survived, so the test pinned submission, not the join. Verified the
  rewrite kills that mutant (finding 2).
- The design's residual ceilings also record that a context close lets
  the pool's queued rows run on its non-daemon threads: destroy()
  returns immediately, process exit waits, every such row stays fenced
  and re-drivable (finding 3).

* test(managed-agent): count #15448's two permission_rules columns in the CSI inventory

#15448 added permission_rules_json to ma_session and ma_turn without
bumping this test's column sum, which is latent-red on main and broke
the merged fast tier here. Local chain: red at 747 expected/749 actual,
green at 749. The same literal lands for main in the open #15455, so
this edit merges conflict-free against it.

Refs #15442
2026-10-25 15:49:55 +08:00
e9b09e1c82 fix(test): budget the standalone runtime suite for the release pool (#15102)
* fix(test): budget the standalone runtime suite for the release pool (#15091)

The new Node/Bun regression suite runs two sequential workers that each spawn hundreds of probes. Release run 39621619809 lost both attempts at the 300 s per-worker and 600 s total spawn caps on the release pool, even though the same suite completes in about five minutes on a developer machine.

Raise the per-worker budget to 600 s and the total budget to 1500 s so release validation can finish on the shared runner. The job's own 30-minute timeout remains the upper bound.

* fix(test): fit the runtime budget inside the release job (#15091)

The old caps were never both hit: in release run 39621619809 the node worker passed at 294 s while the bun worker was SIGTERM'd by the 300 s per-worker cap at 301 s, and the child exited before the 600 s outer cap. Correct that attribution in the budget comment.

Derive the outer cap from the two sequential workers plus setup, and pick a 540 s worker cap so the derived 1140 s spawn still fits the 30-minute quality_scripts job beside the measured ~295 s setup and ~288 s scripts suite. A release-workflow meta-test pins the derivation, the worker/outer ordering, and the job-budget fit.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(test): classify the OpenTUI parity suites for the no-AK gate (#15091)

Main's OpenTUI migration added three interactive TUI parity suites, but the
no-AK membership guard requires every runnable integration test to be either
selected or explicitly exempted. The merged tree therefore failed the Test job
at no-ak-integration-ci.test.js:378.

The three suites are heavyweight interactive tests already covered by the
three-leg tui-parity workflow, so exempt them as "Runtime budget" alongside
the existing tui-migration and interactive-session-viewport entries.

* fix(ci): preserve enrolled parity suites in runtime budget repair

* test(scripts): pin standalone runtime timeout wiring and budget envelope

Cover the outer-margin floor, both spawnSync timeout bindings, and the package.json hop that places the spawn inside quality_scripts, so the release budget ratchet fails when any of them detaches.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(test): close the standalone runtime budget over setup phases (#15091)

The budget ratchet counted only the child spawn plus a hand-picked reserve, leaving the parent's four downloads and two tar extractions and the child's five pre-worker probes outside the model. Bound each process's setup with a shared SETUP_TIMEOUT_MS, derive the outer cap from it, and assert the closed sum against quality_scripts' 30-minute budget.

Also pin the per-host failure diagnostic the margin protects and the package.json hop that reaches --ci, so removing either fails the ratchet.

Co-authored-by: Qwen-Coder <[email protected]>

* test(scripts): make the runtime cache fetch pin shape-insensitive (#15091)

Co-authored-by: Qwen-Coder <[email protected]>

* test(scripts): enforce the setup budget through one factory (#15091)

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen Autofix <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: 易良 <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
2026-10-25 15:47:18 +08:00
Barry 12c620e314 test(ci): pin JUnit report upload paths (#15502)
Guard the CI artifact contract by resolving each JUnit writer against its Vitest root and requiring the package-level upload location.

Tested: targeted Vitest, ESLint, Prettier, build, typecheck, diff check
2026-10-25 14:45:46 +08:00
Aling33 195029c12c ci(sdk-java): upload Surefire text reports on test failure (#14910)
* ci(sdk-java): upload Surefire text reports on test failure

* ci(sdk-java): quote if-no-files-found for yamllint
2026-10-25 06:38:25 +00:00
易良andzzj fa3d9027c0 test(managed-agent): wait for worker exit visibility (#15503)
Co-authored-by: zzj <[email protected]>
2026-10-25 06:35:11 +00:00
b3783a36aa fix(serve): probe X11 display reachability before advertising the native directory picker (#15434)
isNativeDirectoryPickerAvailable only checked that DISPLAY or WAYLAND_DISPLAY was set and that some PATH entry held an executable named zenity. A headless host with a stale DISPLAY therefore advertised the native_directory_picker capability and Web Shell rendered a Browse button whose click could only fail with "cannot open display".

For a local ":<n>" DISPLAY the X server is reached through /tmp/.X11-unix/X<n>, so the probe now requires that socket to exist before the tag is advertised. It stays a synchronous existence check -- no spawn, no connection -- to fit the startup probe budget. DISPLAY values that are not a local ":<n>" display keep their existing result, and the WAYLAND_DISPLAY-only path is unchanged.

Refs #9981


Patrol-Run: qwen-issue-patrol/jmvm6asvfj7

Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-10-25 06:17:16 +00:00
jinyeandQwen-Coder 08a06a46bd fix(web-shell): Prevent duplicate Managed composers (#15496)
Co-authored-by: Qwen-Coder <[email protected]>
2026-10-25 14:08:39 +08:00
易良 32b1f12f75 fix(release): bound summary concurrency and refresh release PRs (#15500) 2026-10-25 13:58:22 +08:00
017017f095 fix(cli): stop double-counting reasoning tokens in cost estimates (#15464)
* fix(cli): stop double-counting reasoning tokens in cost estimates

Four sites computed the billable output token count as
`candidates + thoughts`. That is right for the Gemini-style convention where
`thoughts` is disjoint from `candidates`, but OpenAI-style providers already
include reasoning inside `candidates` (and their reported `total` reflects
that), so the reasoning got billed twice.

With prompt=100000, candidates=150000, thoughts=120000, total=250000 the
output came out as 270000 ($0.2700 at $1/M output) instead of the 150000
($0.1500) the provider actually reported.

Derive output as `total - prompt`, which is correct under both conventions,
guarded by `total > prompt` and falling back to `candidates + thoughts` when
no usable total is reported. This mirrors the idiom already in
`commands/review/cost-ledger.ts` and the derivation in
`services/tokenEstimation.ts`; the guard keeps a record whose total is absent
or inverted from billing a negative output.

Sites:
- ui/components/ModelStatsDisplay.tsx (hasPricing probe + Estimated row)
- ui/opentui/item-projection.ts (entryCost)
- ui/commands/statsCommand.ts (non-interactive /stats output)

No-op on the disjoint convention: for prompt=100000, candidates=50000,
thoughts=120000, total=270000 both derivations give 170000.

Fixes #15444

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-issue-patrol/jmvmj5rjfjg

* fix(cli): trust total - prompt only inside the usage band

The bare `total > prompt` guard read the raw `models` bucket, which
uiTelemetry accumulates without the prompt/total normalization it applies
to statsModels. A record whose prompt did not survive the provider -- a
total-only usage payload, or cached-only input coerced to prompt 0 --
passed the guard and billed input at the output rate; a short total
under-billed. Bound the derivation to the band both usage conventions
allow, and keep summing outside it: whether `thoughts` is folded into
`candidates` depends on the provider's auth_type, which an accumulated
bucket no longer carries, so guessing there would drop a disjoint
provider's reasoning from the bill entirely.

One rule in one place: the four cost sites now share billableOutputTokens
in costCalculator instead of four hand-copied ternaries.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvmqaysjth

* fix(cli): bill /stats model per source bucket, not the model aggregate

R2-1: `billableOutputTokens` is a clamp, so it is not additive --
f(sum of buckets) != sum of f(bucket). The three panel sites bill the
per-(model, source) buckets that `flattenModelsBySource` renders, but
`statsCommand.ts` billed the per-model aggregate, so the non-interactive
`/stats model` figure could disagree with the panel and fall straight back
into the double count this PR removes.

Measured counterexample (two buckets of one model, output at $1/M):
  main       {prompt:100000, candidates:150000, thoughts:120000, total:250000} -> f=150000
  researcher {prompt:0,      candidates:10000,  thoughts:2000,   total:52000}  -> f=12000
  sum of buckets = 162000 ($0.1620)  <-- what the panel shows
  aggregate      = {prompt:100000, candidates:160000, thoughts:122000, total:302000}
  f(aggregate)   = 202000 ($0.2020)  <-- what /stats model printed

Sum the same buckets the panel renders. `Object.values(bySource)` with an
aggregate fallback mirrors `flattenModelsBySource`, so the two surfaces stay
equal in every case it distinguishes (empty bySource, main-only collapse,
multi-source split).

Adds one leg pinning it. Verified against three individually-applied
mutants, each restored byte-identical afterwards (baseline 41 passed):
  M1 revert to f(aggregate)          -> 1 failed ($0.1620 leg)
  M2 plain bucket candidates (R2-2)  -> 1 failed ($0.1620 leg)
  M3 pre-PR candidates + thoughts    -> 2 failed (both #15444 legs)

Also normalizes one blank line in the merged item-projection.test.ts so the
file is prettier-clean.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvn35xgdtw

* fix(cli): gate billableOutputTokens band on totalMissing to keep the derivation monotone

When a telemetry bucket mixes events with and without total_tokens,
the cumulative total lags behind prompt/candidates. The band
[candidates, candidates+thoughts] could still admit the understated
derived value, making the cost estimate go down after additional
usage — turning a monotone quantity non-monotone.

Track totalMissing at the accumulation site (uiTelemetry.ts:653-656)
and gate the band on !totalMissing in billableOutputTokens. When a
bucket saw any total-less event, fall back to candidates + thoughts
rather than trusting a total that may be incomplete.

* fix(cli): accumulate billable output per API response

* fix(cli): preserve raw usage schema and verify zero-cost visibility

---------

Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-10-25 13:54:13 +08:00
易良andxxwtiancai f28fa001ef fix(hosted): preserve primary MySQL isolation failures (#15505)
(cherry picked from commit f9e69ebe69)

Co-authored-by: xxwtiancai <[email protected]>
2026-10-25 13:52:39 +08:00
ChiGao 4339775208 fix(cli): preserve OpenTUI clipboard source across draft recovery (#15396)
* fix(cli): align OpenTUI cancellation and retry with Ink

* fix(cli): discard abandoned OpenTUI tool attempts

* feat(cli): preserve OpenTUI session replay and rewind

* fix(cli): preserve checkpoint restore ownership and cold continuation

* test(cli): cover committed conversation rewind across renderers

* feat(cli): capture and restore OpenTUI checkpoints

Candidate checkpoint before parent test integration; full normal and combined product validation are still pending.

* test(cli): remove duplicate checkpoint regression declarations

* fix(cli): preserve Ink compact tool summaries after restore

* fix(cli): preserve OpenTUI clipboard source through draft recovery

* test(cli): cover OpenTUI raw recall and framed Vim submissions

* fix(ci): register clipboard regression in TUI parity gate

* fix(cli): preserve logical restore identity across authorized edits

* test(cli): assert saved restore call identity in permanent acceptance

* test(cli): align native tool result provenance assertion
2026-10-25 13:51:23 +08:00
易良 b3b17eb572 fix(web-shell): keep permission rule declarations self-contained (#15501) 2026-10-25 13:51:04 +08:00
52b0574b25 test: capture expected disk-full logs (#8549)
* test: capture expected disk-full logs

* test(acp): capture rewind persistence warnings

---------

Co-authored-by: xingjianding1-collab <[email protected]>
Co-authored-by: 易良 <[email protected]>
2026-10-25 05:04:17 +00:00
15aae44b36 test(integration): pin removeScratchDir's retry budget with a witness (#15488)
removeScratchDir's rm() budget (maxRetries: 5 / retryDelay: 200) was
unpinned: mutating it to 0 kept every test green, so a regression that
removed retrying entirely would ship silently. The existing ENOTDIR case
cannot catch it -- ENOTDIR is outside rm's retryable set -- and asserting
only that the promise resolves is vacuous, because the catch swallows
every rejection.

Add a success-path case with a self-limiting straggler refill writer.
Seeded entries widen the removal walk so a refill lands between readdir
and rmdir (ENOTEMPTY, retryable), and the writer is capped by both a file
ceiling and a wall-clock cutoff so it goes quiet well inside the shipped
budget. The load-bearing assertions are that the tree is actually gone
and that no warning was logged.

Red witness: setting maxRetries to 0 in scratch-dir.ts fails this case on
2/2 runs while the ENOTDIR case still passes; scratch-dir.ts is restored
byte-identical (md5 3cdb5f2f504ea6cc7663e2708ae133f4).

Refs #11310


Patrol-Run: qwen-issue-patrol/jmvn0b1rrjt

Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-10-25 12:52:48 +08:00
易良andQwen-Coder cf3ba47dfd fix(ci): file main CI failures whose successor has not settled (#15471)
* fix(ci): file main CI failures whose successor has not settled

The guard added in #15272 treats a queued or in-progress successor at
main's tip as owning the result, so the report is skipped. A successor
that later ends cancelled, timed_out or skipped never re-enters the
workflow — its gate admits only failures — so the failure is never
reported at all.

That is how 10 main-push failures between 14:00Z and 17:30Z on
2026-10-24 produced no issue (#15470), with 65 of the last 100 Qwen
Code CI push runs on main ending cancelled.

The reporter cannot wait for the successor either: it runs on
ubuntu-latest, and that pool queued it for 20-100 minutes in the same
window. Report instead, marked provisional, since the per-signature
route hands new issues to a human rather than to autofix: a stale
provisional report costs a close, a dropped one costs the regression.

* fix(ci): keep a provisional head off the infra rerun path

Four review findings on d98bc2d1ca:

- rerun_infra holds actions: write but was gated only on infra_only and
  file_issue. An unsettled successor now yields provisional=true instead
  of superseded=true, so destination runs and file_issue becomes true: an
  infra-only failure at a head main already replaced would dispatch
  rerun-failed-jobs onto the contended pool this PR cites (#15470), and a
  successful dispatch would then satisfy file_issue's
  `rerun_infra.result != 'success'` operand, so nothing was filed and no
  step summary was written either. Add the provisional operand; skipping
  the rerun is what makes file_issue record it instead.
- Only a settled successor can own the result, so invert the case arm:
  anything but `completed` (and `none`) is provisional. The enumeration
  covered queued and in_progress only, leaving waiting, requested and
  pending to file without the caveat and to warn falsely that the
  successor "does not report".
- The caveat heads the comment routes as well as the create routes, so
  "this issue can be closed" was advice to close a tracker belonging to a
  different occurrence. Reword to "this report can be dismissed".
- Pin both YAML hops of the provisional wire; retyping either left
  PROVISIONAL empty and the suite green.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvmwqg38tp

* test(ci): cover the provisional caveat on its two untested write sites

The caveat is prepended once ahead of the route split, so it reaches three
write sites: the per-signature certified-miss create, the per-signature
recurrence comment, and the aggregate route. Only the first was exercised
with provisional=true, so narrowing the prepend to a single branch would
drop the caveat from the other two unnoticed.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvn35xgdtw

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-25 12:51:30 +08:00
1a98854b76 test(ci): pin the disk-pressure collector revision and FULL_PAYLOAD run bodies (#15475)
* test(ci): pin the disk-pressure collector revision and FULL_PAYLOAD run bodies

Two deferred review findings from #10919 (issue #11032). Both are
assertion-only: no workflow file is touched.

The shared-prelude equality loop compares `test` and `lint_and_static` to
each other, so both jobs' disk-pressure collector moving to a different
`actions/upload-artifact` revision together leaves both serializations
equal and reports green. The SHA-format regex on `Upload Test Results
Artifact` is not a witness either: it accepts any 40-hex revision, on a
different step. Pin the literal revision instead, resolved per job
because the collector step carries the same name in both.

The FULL_PAYLOAD test pinned name, order and gate, but only the two
notices steps had a command body pinned: emptying `Run ESLint`'s `run:`
or repointing it at `true` left every suite green while a required check
stopped running the tool it is named for. Extend the existing loop with a
name -> load-bearing-substring map that is total over FULL_PAYLOAD, and
assert the substring rather than whitespace-exact text so reformatting
one of the multi-line blocks stays benign.

Verified by mutation in both directions. Against the pre-change suite,
moving the collector pin in both jobs, emptying `Run ESLint` and
repointing it at `true` all stayed green (44/44) -- that green was the
defect. With these assertions the same three mutations fail, and the
unmutated suite passes 45/45.

Refs #11032

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-issue-patrol/jmvmng32bjj

* test(ci): pin collector retention, payload gates and failure propagation

Addresses the four review findings on the pins this PR adds, all of them
the same class of gap: an assertion that proves a string is present but
not that the thing it guards still happens.

R1-1: `with.retention-days` was unpinned on both disk-pressure collectors,
so a coordinated edit reported green through the job-to-job equality loop
(the loop is blind to both sides moving together). Pinned per job as the
literal 14; the JUnit `Upload Test Results Artifact` step deliberately
retains 7, so no shared constant.

R1-2: map totality over FULL_PAYLOAD was only incidental — `toContain`
coerces a missing entry to the literal string "undefined", which a body
containing that word passes silently, and a stale key after a rename reads
as coverage that no longer exists. Asserted the key sets match instead.

R1-3: payload gates were filtered by a `ci_profile == 'full'` substring, so
appending any further `&&` clause dropped a step from the unattended
post-merge lane while every substring still matched. All 19 gates are
byte-identical, so pin the whole literal.

R1-4: containment proves the command is present, not that its failure
reaches the job status. Pinned `continue-on-error` absent and no
`|| true`/`|| :` in the body, plus `exit 1` in the settings-schema gate.
Both stay inside the loop: `Report test growth` and `Fetch incremental
lint history` set continue-on-error deliberately, and the helper-tests
retry legitimately uses `|| {`.

Each new assertion was shown to fail by mutating `.github/workflows/ci.yml`
one edit at a time: the six gap mutants pass 45/45 against the previous
head and each is killed by exactly the assertion added for it. The
one-sided retention control still trips the pre-existing equality loop, so
those greens were coordinated-edit blindness rather than a dead comparator.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvmwqg38tp

---------

Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-10-25 12:50:50 +08:00
ChiGao c7c9717d07 fix(cli): run OpenTUI on private Bun 1.4.4 (#15387)
* fix(cli): run OpenTUI on private Bun 1.4.4

* docs(cli): bind runtime acceptance to integrated main

* test(cli): add permanent installed npm runtime acceptance

* ci: record installed TUI workflow size

* ci: record installed TUI job budget

* ci: quote missing installed artifact failure mode

* fix(core): preserve encrypted storage identity across Bun handoff

* fix(test): retain installed command startup evidence

* fix(cli): reject Bun loader options during npm handoff
2026-10-25 11:50:17 +08:00
ChiGao 17b8efa953 feat(cli): capture and restore complete OpenTUI checkpoints (#15389)
* fix(cli): align OpenTUI cancellation and retry with Ink

* fix(cli): discard abandoned OpenTUI tool attempts

* feat(cli): preserve OpenTUI session replay and rewind

* fix(cli): preserve checkpoint restore ownership and cold continuation

* test(cli): cover committed conversation rewind across renderers

* feat(cli): capture and restore OpenTUI checkpoints

Candidate checkpoint before parent test integration; full normal and combined product validation are still pending.

* test(cli): remove duplicate checkpoint regression declarations

* fix(cli): preserve Ink compact tool summaries after restore

* fix(cli): preserve logical restore identity across authorized edits

* test(cli): assert saved restore call identity in permanent acceptance

* test(cli): align native tool result provenance assertion
2026-10-25 11:20:08 +08:00
ChiGao f520bac55c fix(cli): preserve checkpoint restore ownership and cold continuation (#15386)
* fix(cli): align OpenTUI cancellation and retry with Ink

* fix(cli): discard abandoned OpenTUI tool attempts

* feat(cli): preserve OpenTUI session replay and rewind

* fix(cli): preserve checkpoint restore ownership and cold continuation

* test(cli): cover committed conversation rewind across renderers

* fix(cli): preserve logical restore identity across authorized edits

* test(cli): assert saved restore call identity in permanent acceptance

* test(cli): align native tool result provenance assertion
2026-10-25 08:50:52 +08:00
ChiGao b44e033315 feat(cli): preserve OpenTUI session replay and rewind (#15384)
* fix(cli): align OpenTUI cancellation and retry with Ink

* fix(cli): discard abandoned OpenTUI tool attempts

* feat(cli): preserve OpenTUI session replay and rewind

* test(cli): cover committed conversation rewind across renderers

* test(cli): align native tool result provenance assertion
2026-10-25 06:43:22 +08:00