Commit Graph
12046 Commits
Author SHA1 Message Date
Bob.qwencodeandgithub-actions[bot] 208beb2b3b chore(release): v0.26.9 [skip ci]
* chore(release): v0.26.9

* docs(changelog): sync for v0.26.9

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-11-06 18:03:10 +00:00
易良andQwen-Coder 8660f75430 fix(ci): refresh host assets on older PR checkouts (#16476)
* fix(ci): refresh host assets on older PR checkouts

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

* style(ci): format host asset regression cases

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-07 01:42:26 +08:00
Shaojin Wen ad83860092 feat(web-shell): move session search beside the activity toggle (#16467)
* feat(web-shell): move session search beside the activity toggle

The sidebar search lived in the Workspaces header, where it appeared only
on hover and moved its input into a separate row. Put the toggle in the
top brand row next to View activity, expand the field inline when the
column is wide enough, and fall back to a full-width row under the brand
row for narrow drawers and columns. Hide the control while the activity
view replaces the session list, and keep the collapsed strip's search in
its session popover.

* test(web-shell): scope the drawer brand assertion to the logo

The always-visible search toggle adds a second SVG to the sidebar brand
row, so the drawer smoke test parent-level svg locator matched both the
logo and the search icon and failed in strict mode. Target the logo own
aria-hidden container instead.
2026-11-06 17:38:27 +00:00
易良andQwen-Coder cb282ef5cf test(web-shell): unit-test settings development route (#16456)
Replace the full Vite dev-server startup with a direct middleware test so the settings route contract no longer depends on Babel and App.tsx cold-start time.

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-07 00:44:10 +08:00
0964c4bc3c test(serve): cover the workspace_runtime_stop boot-time conjunction (#16469)
The stop surface is advertised only when five boot-time legs hold:
managedChildProcesses, workspaceRuntimeRemoval, a live ACP http handle,
manageScheduledTaskSessions, and a managed runtime whose bridge exposes
stopWorkspaceRuntime/getRuntimeStopCompletion/getRuntimeStopSnapshot.

Nothing exercised that computation from /capabilities: the only existing
references inject the workspaceRuntimeStopAvailable toggle directly, so
any single leg could regress while the suite stayed green. The new test
mounts the real app with every leg wired and asserts the feature is
advertised, then drops one required leg and asserts it is not.

Part of #12563, does not close it.


Patrol-Run: qwen-issue-patrol/jmw51qoerzl

Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-11-07 00:42:42 +08:00
jinyeandQwen-Coder 45cde86998 feat(cli): align managed agent basic tools with daemon (#16445)
Expose glob in ordinary file sessions, durable todos in Shell sessions, and PDF page selection in Hosted read_file. Restore saved todo state on session reload while keeping exact current-schema validation and restricted resident/CSI tools.

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-07 00:13:05 +08:00
Shaojin Wen 478c2f87ab feat(ci): make flaky-patrol rerun and no_action decisions visible (#16459)
* feat(ci): make flaky-patrol rerun and no_action decisions visible

The CI Failure Patrol posted only a hidden HTML marker when it re-ran a
flaky failure or could not classify one, so maintainers saw a red PR with
no explanation and clicked Update Branch — advancing the head, dismissing
approvals, and re-running the whole CI for a failure that was already
being handled on the same commit.

Post a short bilingual notice alongside the marker for both decisions:
the rerun notice states the transient cause and that no Update Branch is
needed; the no_action notice asks for manual review before rerunning or
updating the branch. The deterministic-failure comment is byte-identical,
and the post-success reset marker stays hidden.

Pin the visible notices, the marker round-trip through dedup and the
per-head action count, and reason escaping for all three actions.

* fix(ci): strip bidi overrides from flaky-patrol notice reasons

An adversarial audit of the visible patrol notices found that bidi
control characters (U+200E/U+200F, U+202A-U+202E, U+2066-U+2069) passed
through the reason escaping untouched. The reason is classifier output
derived from untrusted CI logs, and a right-to-left override can visually
rewrite the very sentence maintainers trust when deciding whether to
update a branch.

Drop those characters in the shared escaping helper so all three visible
decisions (and the deflake issue signature) are covered, and pin the
stripping for each action. Removing the strip line fails the new test.

* fix(ci): cover the Arabic letter mark in the patrol bidi strip

An escalated audit round compared the strip class against the full
Unicode Bidi_Control set and found U+061C (Arabic letter mark) missing;
it can reorder adjacent Arabic-script text in a visible notice. Add it
to the class and extend the per-action pin payload, so the class now
matches the complete Bidi_Control set. Removing just U+061C fails the
pin test.

* fix(ci): collapse newlines in patrol reasons and pin the audit gaps

The verification round found that a newline plus a line-start fence in a
classifier reason opens a block-level code fence in the visible notice:
the folded Chinese block and the tracking marker render as visible code.
Collapse newlines and their surrounding whitespace to a single space in
the shared escaping helper, keeping every reason single-line.

Close the coverage gaps the same round measured: feed all twelve
Bidi_Control code points individually, pin the English-before-fold and
Chinese-inside-fold placement of the deterministic comment, and assert
the fold's closing tag in all three notices.

Witnesses: removing the collapse, narrowing the bidi class, dropping
U+200F alone, swapping EN/ZH, or dropping the closing tag each fails the
suite; a marked rendering probe shows the fence swallowing the fold
before the fix and a clean render after; benign comment bodies remain
byte-identical to base.
2026-11-06 23:10:16 +08:00
callmeYe 99c2e28d3a fix(web-shell): show readable Skill completion details (#16451) 2026-11-06 14:58:27 +00:00
liuxuely0626andluoyun ee1a9b1e1e fix(ci): move audio prebuilds to macOS 15 (#16464)
Co-authored-by: luoyun <[email protected]>
2026-11-06 22:47:15 +08:00
7cb46d9fed feat(hooks): add FileChanged notifications for local sessions (#13969)
* feat(hooks): add FileChanged notifications for local sessions

* fix(ci): preserve shell gate coverage and trim transcript data

* fix(ci): flush debug prompts and isolate git fixtures

* fix(hooks): respect ACP watcher ownership and skill registration

* test: stabilize hosted recovery and failed spawn fixtures

* fix(ci): budget the complete hosted integration fork

* fix(runtime-broker): accept MariaDB CSI guard metadata

* test: align schema baseline and hook dispatch waits

* fix(ci): allow hosted shared fork to finish all classes

* fix(hooks): preserve file watching across errors and cwd changes

* fix(ci): isolate reference archive extraction per run

* test(ci): keep reference archive checks dependency-free

* fix(hooks): constrain recursive watches and repair CI

* test(web-shell): synchronize verified connection return

* fix(ci): correct runtime broker callback indentation

* test(web-shell): match the managed workspace registration label

Keep the remote workspace registration smoke checks in sync with the service label added by unified workspace navigation. Preserve exact text matching and all registration and replay assertions.

* fix(ci): reduce standalone runtime fixture overhead

* fix(hooks): ignore implicit atomic-write temporary files

* test(managed-agent): recover initial node port conflicts

* fix(hooks): observe parents before discovery anchors disappear

* test(managed-agent): await stream cleanup before recovery teardown

* test(managed-agent): align hosted shell fixture capabilities

* test(managed-agent): let OS choose initial harness port

* fix(ci): restore runner-local Java cache permissions

* test(runtime-broker): use native execution profile paths

* test(managed-agent): bind valid workspace upload probes

* test(managed-agent): reuse the upload probe digest fixture

* test(managed-agent): bind hook fault listeners to available ports

* test(managed-agent): align the native profile path fixture

* test(managed-agent): Wait for projection refusal retries

* ci: Prepare daemon Java cache before Node preflight

* test(managed-agent): check channel model calls after delivery

* fix(hooks): observe explicitly registered symlink targets

* test(managed-agent): separate consumption from relay snapshots

* test(managed-agent): target tamper injection by resource key

* test(managed-agent): exhaust attestation EOF retry in fault gates

* fix(ci): bound RocketMQ release downloads

* test(tui): await the restored assistant before capturing history

* fix(ci): use a checksum-pinned RocketMQ mirror

* fix(ci): provision missing browser test fonts

* fix(ci): quote the font probe environment

* fix(ci): prepare browser fonts without elevation

* test(core): compare complete prune reports without ordering

* fix(ci): repair only runner-local Java tool caches

* test(managed-agent): qualify fixture JDBC templates

* fix: isolate reconnect hooks and cancellation test fixtures

Skip chat initialization so MCP reconnect cannot re-enable file hooks, and keep cancellation fixtures isolated from the background worktree scanner.

* fix(ci): tolerate a missing Java cache helper on resumed releases

Resuming an existing release checks out a tag that may predate the helper, which failed all five Java jobs before setup-java ran.

* fix: finish the main merge for harness deadlines and workflow size

Use the renamed settle-timeout accessor in the merged hosted-harness tests,
and record sdk-java.yml's new size after the resumed-release cache guard.

* style(managed-agent): restore import grouping in merged verification tests

* test(managed-agent): require consumed child result mirrors

* test(web-shell): center repository settings capture

---------

Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: yungsen.hys <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
2026-11-06 21:43:01 +08:00
易良andQwen-Coder e61ad3959e fix(managed-agent): settle a title the dispatcher claimed first (#16447)
* fix(managed-agent): settle a title the dispatcher claimed first

renameSession admits the RENAME row and then claims it. The background
SessionRenameCoordinator scans rows a request has just admitted, so its
claim can win without any concurrent caller, and the request answered
503 "The Session title is already being delivered." for its own rename.

Wait a bounded 2s for that delivery to settle and answer the recorded
command instead of failing the request.

Fixes #16431

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

* fix(managed-agent): keep the creator gate on the rename settle arm

The settle arm answered the recorded rename outcome without re-running
the replay actor gate, although it shares the COMPLETED read-back's
TOCTOU window: a pre-read that missed the recorder's row lets a
non-creator OPERATOR reach the settle through the duplicate-key winner
path and take a 200 the COMPLETED arm refuses with 409. Mirror that
arm's gates — requireReplayActor on the settled Session, and the
last-visible serialization — so both arms answer identically.

Test: a non-creator OPERATOR racing the recorder's key through the
settle arm meets the same 409 workspace_unavailable as through the
COMPLETED arm, while the recorder still replays its own outcome; the
dispatcher's completion is asserted so the refusal provably comes from
the settle arm. Load-bearing: reverting the gate answers the raced
request 200 and fails the test.

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-06 21:15:12 +08:00
易良andQwen-Coder 989d31ee9e test(managed-agent): retarget the lifecycle IT and retry snapshot claim deadlocks (#16446)
The publication-lane IT still parked the receipt thread on the placement
guard's `INSERT INTO ma_tenant_guard`, but #16354 moved ordinary publication
lanes onto the Session root, so that statement never runs and the test now
fails on its own latch instead of on a product lock cycle. Park the receipt
on the root statement its lane actually executes.

The sibling native-snapshot IT exposed a real gap: two publishers of the same
snapshot claim the manifest row with `INSERT ... ON DUPLICATE KEY`, and InnoDB
can resolve that by rolling one side back as a deadlock victim. Retry the
claim a bounded number of times so the loser reads the winner's row and
answers the replay descriptor instead of surfacing a deadlock.

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-06 21:15:04 +08:00
易良 1ecf444010 test(interactive): poll the foreground-shell frame instead of snapshotting it (#16455)
#15712 recurred on 2026-11-05 (run 42069115052, macOS): the case asserted
`compact(frame('mode-before'))` contains `YOLO mode` straight after a PTY
write, and on a loaded host the approval footer had not repainted yet — the
captured frame still held the startup banner, so the assertion failed on a
correct UI. The draft snapshot just above it has the same shape.

Poll both through the suite's `until`, which already scales its budget 3x
under CI (#16276), so a slow repaint is waited out while a genuinely missing
footer still fails on the same message, with the frame kept in FRAMES.json.

Verified locally: the case passes (16.9s) and the whole file passes
(3 passed, 1 skipped).
2026-11-06 20:28:34 +08:00
1853b1505f fix(subscription): isolate activation and restore sign-in recovery (#16453)
* fix(web-shell): wait for session before checking subscription commands

* fix(cli): keep signed-out subscription startup in the composer

* fix(subscription): preserve model selection across sign-in

* fix(subscription): align sign-in with model selection scope

* fix(subscription): apply reasoning defaults across models

* fix(subscription): tag Qwen web links by client

* fix(subscription): prefix Qwen web client tags

* fix(subscription): attribute Qwen web links by paywall scene

* fix(subscription): recover historical sessions with saved API models

* fix(cli): remove the signed-out subscription startup notice

* fix(subscription): support standalone desktop activation and expose errors

* fix(subscription): isolate activation and session model selection

* fix(subscription): enforce auth policy and restore home login

* test(cli): preserve the default initializer mock on restore

---------

Co-authored-by: luoyun <[email protected]>
Co-authored-by: 易良 <[email protected]>
2026-11-06 20:11:52 +08:00
chencheng (云谦) 1b1f4455d9 ci: build the PR tree once and shorten the required path (#16450)
* ci: build the PR tree once and shorten the required path

The no-AK integration gate, the four web-shell smoke shards and Lint & Static each rebuilt the tree test_build already uploads, and the build cache rarely helped: most runs logged "29 of 29 workspaces changed" because the arch-keyed cache was split across the mixed x64/ARM64 pool and a prefix restore returned the newest entry instead of the base commit's.

- test_build restores the build its base commit saved (ci-build-cache.mjs key --at), drops runner.arch from the key, and runs the serve fast-path closure and core subpath checks against the built tree.
- The no-AK integration gate unpacks that tree and runs as three shards; integration_no_ak_result keeps the required check name.
- The web-shell smoke unpacks the same tree instead of building; consumers recopy the opentui native assets for their host (host-assets).
- The standalone runtime regression moves from unit shard 1 to shard 4.
- A java_only profile skips the TypeScript lanes for changes confined to the Java packages; Lint & Static runs the scripts/tests suites that name them.
- Drafts skip the integration gate and the browser smoke; ready_for_review starts the full run.

* ci: check the settings schema in test_build, which has the core dist

Lint & Static only had core's dist because the serve fast-path check built
it; with that check moved, Generate settings schema failed to import it.

* ci: keep the standalone regression on unit shard 1

Moving it did not shorten shard 1: the regression runs in the side lane,
in parallel with the CLI and core suites, and shard 1's time is its CLI
slice (576 s against 147 s on shard 4 for the same 181 files). That
imbalance is left to duration-based sharding.
2026-11-06 03:30:11 -08:00
Fede Kamelhar 1d64177286 docs(providers): document OCI Generative AI under OpenAI-compatible providers (#16345)
* docs(providers): document OCI Generative AI under OpenAI-compatible providers

Adds a subsection to model-providers.md covering OCI Generative AI's two
OpenAI-compatible base URLs (Chat Completions and Responses), Bearer
API-key auth, the publisher-prefixed model ids, a ready-to-paste
settings.json, and the endpoint behaviours a user otherwise has to
discover by trial: pinned contextWindowSize, no model discovery, the
max_completion_tokens requirement of GPT-5 / o-series on Chat
Completions, the Responses-wire requirement for gpt-5.6 / GPT-6 tool
calling, gpt-oss staying on Chat Completions, compressed error bodies,
and imported models addressed by endpoint OCID.

Refs #16332 (docs-first path recommended in triage).

* docs(providers): list the API-key regions for OCI Generative AI

The OCI section named `uk-london-1` among the regions that serve the
OpenAI-compatible endpoints and omitted Hyderabad and Osaka. The API Keys
page is the source of truth for the Bearer-key path this section
documents, and it lists Frankfurt, Hyderabad, Osaka, Ashburn, Chicago and
Phoenix; London is only on the IAM-auth agentic-inference list. Name
those six regions and link the API Keys page instead of the agentic
regions page, since it also lists the model families the key covers.
2026-11-06 11:05:46 +00:00
jianyx1 f9124d6b1d test(managed-agent): cover pending sibling approval holds (#15994) 2026-11-06 18:42:53 +08:00
jinyeandQwen-Coder e777fc7502 feat(managed-agent): enable automation by default (#16448)
* feat(managed-agent): enable automation by default

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

* style(docs): match pinned Prettier formatting

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-06 18:42:51 +08:00
Lanre Shittu 3b39fc2a19 fix(core): accept images for Qwen VL models under their Ollama names (#16309) 2026-11-06 18:31:19 +08:00
jianyx1andqwen-code-dev-bot a051a6a796 fix(web-shell): batch session deletions at the route limit (#14637)
* fix(web-shell): batch session deletions at the route limit

* test(web-shell): cover bulk delete in visual previews

* fix(web-shell): retain bulk delete interruption errors

* fix(web-shell): retain deletion failures across workspace batches

Collect per-session and request failures throughout a deletion without losing
later causes or earlier successful current-session cleanup. Keep archive
behavior unchanged and document the daemon request-limit coupling.

Add gated regressions for both cross-workspace failure orders and multiple
per-session errors across batches. The new gates reject parallel-workspace
and per-workspace-error-reset mutations.

* test(web-shell): cover bulk delete merge boundaries

Preserve workspace and standalone failure causes for per-session and rejected requests. Verify each deletion batch captures its UI owner before the request and invokes its own invalidation callback with that batch result.

---------

Co-authored-by: qwen-code-dev-bot <[email protected]>
2026-11-06 10:28:24 +00:00
jianyx1 666d0cae7b fix(web-shell): preserve wrapped drafts during arrow navigation (#14909)
* fix(web-shell): preserve wrapped drafts during arrow navigation

* fix(web-shell): handle bidi composer history boundaries

Compare caret and document edges by visual row so RTL and mixed-direction
drafts can reach history without breaking soft-wrap caret movement.

Cover genuine edits after history recall in both arrow directions and make
the selected-history regression exercise the single-line paging branch.

* fix(web-shell): reset navigation on history selection

Start a fresh navigation sequence when selecting a search or panel result,
preserving browse suppression while dropping stale prompt draft tags.

Cover prior paging, selected-draft restoration, and visual row decisions
with focused unit and browser regressions.

* fix(web-shell): keep focus on recalled prompts

Consume ArrowDown while an explicit recall has no next history entry,
keeping ordinary footer navigation after returning to the draft.

Cover both prior paging states and the subsequent normal footer handoff.
2026-11-06 18:23:56 +08:00
ff775787f5 feat(channels): journal private bound-session delivery and scan health (#16237)
* feat(channels): journal private bound-session delivery and scan health

The event timeline could not tell the private delivery lane's story. A
prompt injected into a PR-bound session ended on the same `running` word
as an event still in flight, no task row named the session it reached,
and the three private watch retires plus the dead-letter left only stderr
behind — so a silent consumption looked like a quiet repository. A bound
event rejected by the channel's own gates was worse than invisible: its
skip row was followed by running rows, reading as a delivery.

The private delivery method returned a boolean meaning "settled", which
conflated an injected prompt, a private retire and the public fallback.
It now returns a verdict, and the task runner derives the journal's
terminal word from it, so an event that was never delivered can no longer
be recorded as delivered. Routing, fallback, retry-budget and cursor
decisions are unchanged.

The binding scan is a new hard dependency: while it has never succeeded
the cold-start gate stalls every notification, yet the poll reports ok.
Degraded rows now surface that beside the stderr lines the adapter
already throttles, and on the edge of the existing watch-reconcile flag,
so a permanent outage cannot flood the journal. Attaching this to the
poll detail was rejected: both cold-start gates return before the detail
is set, which is exactly when it would matter.

No new event type and no protocol change — an outcome is already an open
string. The dead-letter's message body and its failure-path payload dump
stay out of the journal. Also labels `failed_ambiguous`, which was
already written but rendered as a raw token.

* fix(channels): make the bound-delivery journal claims true

Review round 1 found sixteen places where the journal, its labels or its
documentation promised something the code did not do. All sixteen are
addressed here; none is deferred.

Three were ordering or attribution defects in the new rows. A watch retire
was journaled before the removal it describes, so a throwing removal left a
terminal claim for an event the next poll re-runs and retires again under a
different reason. Rows written while a delivery is cleared lost the session
they were about, which is the one fact the private lane's attribution adds.
And the pre-claim watch retire sliced the loaded state before saving with no
rollback, so a failed save lost the event with no trace at all — the comment
claimed the opposite.

Two were counts that measured something other than their label: the partial
scan published the routing map's size, which counts bound PRs, as a session
count; and the streak advances per binding scan, not per poll, because a
gone-session retry refreshes again inside the same poll. The session count now
comes from the scan's own rows, and the streak is labelled as scans in both
languages and in the stderr lines that share the counter. Re-cadencing the
counter itself would change the throttle timing the previous PR shipped, so
the label moved instead.

Two were missing surfaces: the pending-queue-overflow retire wrote only
stderr and the dead-letter file, and an unreadable watch state was reported
as a pending backlog whose stated remedy cannot clear it. The overflow now
journals a body-free skip, and the watch guard classifies a read failure
separately while its gate stays fail-closed.

The recovery signal the prose promised did not exist: a poll/ok row is
written after every poll that does not throw, including all of an outage, so
it cannot mark the end of one. The watch guard now journals its clearing
edge, and the prose says why ok rows cannot serve.

The rest were documentation and test-coverage corrections: the protocol
reference promised detail.streak on every degraded row when only the
binding-scan rows carry it; the design file map certified the protocol
reference as unchanged while this branch edits it; the override's stated
consequence was backwards; and five guards had no witness or only half of
one. Each now has a test, and the suite grew from 438 to 444 in the GitHub
package.

Verified by 37 mutations, all killed. Two initially survived and were
investigated rather than accepted: a loose sessionId count that three
unattributed-row-free rows still satisfied, and a filter that matched no test
after a rename. Both were tightened until the mutation died.

* docs(channels): align the journal ledger with the round-2 verification

The validation section still cited the round-1 mutation count after the
review fixes added fifteen more, and the Chinese half had lost the count
altogether during the rewrite. Both now state 37 applied and 37 killed.

Also records why two mutations initially survived and what was done about
them, since a sweep that cannot distinguish "no test ran" from "the guard
held" is not evidence: a loose sessionId count that three correctly
attributed rows still satisfied, and a filter left matching nothing by a
test rename. The Web Shell line now mentions the absence coverage too.

* fix(channels): keep the lost bound session named on every journal row

The second review round of the bound-session journal found fifteen gaps.
Three were consequential, and two of those were introduced by this PR's own
previous round:

The gone-session retry never updated the local delivery, so with two
followers both gone the terminal row named the first one — a session evicted
a step earlier — while the row above it named the last. An operator reading
"which session lost this event" would open the wrong workspace.

Remembering the target only for the transition that clears the delivery is
not enough: once the clear is persisted, every later poll re-enters the
absence gate with no delivery to read, and the dead-letter at the deferral
horizon reads a record that has none either. A driven run of that sequence
left 239 of 242 rows unattributed, with the terminal row naming nobody — the
one fact this lane's attribution exists to supply. The lost target is now
persisted as attribution only; the delivery is deliberately not restored,
since that would re-enter bound delivery and change routing.

The overflow retire was journaled before its queue shift was durable, with
no rollback, so a failed save left a skip row for an event a restart would
later deliver. It now saves before it journals and restores the shift when
the save throws, matching the sibling pre-claim path.

Also: the partial-scan session count now includes the sessions retention
keeps routing (a draining workspace lists none while still delivering to
three), and both pre-claim retires carry the frozen target so the timeline
and the dead-letter agree about the same event.

Four coverage gaps whose mutants left the whole suite green are closed: the
guard keeping the partial-scan row off the cold-start path, the session on
the no-candidate retire, a second healthy poll so the recovery row's edge
trigger is distinguishable from an unconditional write, and both a shape
test and a Chinese render test for the interpolating labels.

The docs said more or less than the code: detail.sessions is carried by one
row, not by every partial scan; the normative sessionId definition read a
retire chip as a delivery; the emit-point inventory missed the overflow
site; and bindings_partial was described as recovering implicitly when a
successful multi-page scan is its producer. Auditing that last correction
found the stderr census was still incomplete — the lane has five recurring
health and capacity sites, not three — so it now names all five, ranks them
by consequence and states its own scope.

* docs(channels): define what the partial-scan session count actually counts

The count became listed-plus-retained when retention was included, but three
surfaces still described the old value. The protocol reference called it "the
sessions the map still routes to", which is wider than the code: the map also
skips channel-owned sessions and unparseable binding URLs. Both design halves
still said it was "the scan's own row count", which is narrower than the code
and was the exact wording the review asked to correct.

Define it as a session-row count — the rows this scan listed plus the sessions
retention keeps routing, bounded by the retention cap — and say explicitly that
it is not the number of entries surviving the map's own filters. Also record
that "listed" in the row label and in the stderr line is shorthand for
listed-or-retained and that both print the same number, so the wording cannot
quietly drift apart from the value again.

Documentation only: the adapter and its tests are byte-identical to the previous
commit, so every runtime result reported for this branch still applies.

* fix(channels): close the review gaps in the private-lane journal

Address the eight open suggestions and the five deferred findings from
the round-3 review, each with a mutation-verified guard (10 new mutations,
10 killed; one cap-count fixture initially survived because the first
empty partial already capped lastBindings, and was rebuilt so retention
is above the cap exactly at the emitting scan).

- R1-15: the overflow dead-letter append and the sidecar save now fail
  independently. A failed save rolls the shift back, names the watch-state
  path instead of blaming the dead-letter file, drops the payload dump
  (the body is already durable), and remembers the head so the next
  detection skips the re-append. The save-throws test now drives a second
  detection and asserts exactly one dead-letter line.
- R2-6: the pre-claim skip comment and both design languages now say only
  the overflow retire writes a dead-letter record; watch_no_candidate and
  no_role_matched leave the skip row as the event's only trace.
- R2-3: bindings_cold_empty no longer claims implicit recovery — an empty
  partial is a successful scan, the streak resets only on a complete or
  non-empty snapshot, and past the escape the silence is the throttle
  maxing out, not recovery.
- R3-1: a 1100-session fixture pins detail.sessions to the 1024 retention
  cap at the emitting scan; swapping retainedCapped.length for
  retained.length reds it while the two sibling count tests stay green.
- R3-5/R3-6/R3-15: the design's outcome cell lists failed_ambiguous and
  degraded, the watch-guard bullet admits the read-failure error field,
  and the sessionId carve-out covers every row written on a clear plus the
  pre-claim skip; the chip sentence says reached-or-lost. Both languages.
- R3-7: the three private-retire watch tests assert the public path stayed
  empty; leaking the envelope through handleInbound reds them.
- Deferred: the gone-horizon attributes the in-memory task before retiring
  and the dead-letter record names the lost session; the watch-guard row
  re-journals when the blocking cause changes; the subscriptions-off
  recovery edge has a test; the recovery row renders Recovered/已恢复
  instead of Degraded (wire outcome unchanged); a gone-retry fixture pins
  the scan-scoped streak at 10 inside poll 4.

Verified: channels/github 595 passed, web-shell channels 343 passed,
eslint and prettier clean; design mutation ledger updated to 47/47.

* fix(channels): rework the overflow retire ordering and close round-4 review

Critical R4-1: the overflow branch saved the sidecar mid-pass, committing the
advanced baseline before the pass's later events were queued; a crash then
never re-derived them. Remove the mid-pass save entirely — the head is sliced
in memory only, and the baseline and full queue commit together in the single
end-of-pass save, with the skip rows journalled after it.

The dead-letter record is now the retire's commit point. A lazily loaded,
file-backed key set (restart-durable, replacing the process-local map) skips
the re-append (R4-16), and both drains recognize a dead-lettered head and
complete its retire instead of delivering it, so no event is both delivered
and filed as retired undelivered (R1-15 follow-up).

Also addressed:
- R4-12/13/14: terminal, transition lost-session and pre-claim rows carry
  sessionWorkspaceCwd so Open-session reaches a session in another workspace.
- R4-11: the no_role_matched comment states the real ordering — the dedupe
  cursor is in memory until the base saves it, so the skip row is at-least-once.
- R4-15: already_delivered_other_role is documented as a defensive,
  unreachable guard and dropped from the published vocabulary.
- R4-2/3, R3-5: §1 outcome cell adds suppressed/posting, reason covers task
  and poll rows, sessionId carve-out adds cancelled and distinguishes live
  running rows. Both languages.
- R4-4/5/6: the pre-claim census acknowledges the role-aware drain's
  pre-existing fourth retire; the retire-carries-session rationale notes the
  preflight exception; the dead-letter write-failure note names the
  non-recoverable public-suppression exception. Both languages.
- R2-3/R4-7: cold-empty reset is keyed on the routing map and trust flag, and
  the escape's lane-open claim is qualified for the refresh-failure producer.
- R4-8/9: the recurring-site census adds the credential/rate-limit pass abort
  and corrects the no-existing-flag claim for the stale-map refresh failure.
- R4-10: watch-guard error wording covers reconciliation failure too.
- R4-17/18/19: openTarget joins the interpolation guard with a ZH render
  assertion, the EN streak label says consecutive, and ZH degraded/retired/
  recovered labels get render assertions.

Verified: channels/github 601 passed, web-shell channels 331 passed, eslint
and prettier clean; 4 new mutations killed (mid-pass save, file-dedupe, drain
guard, workspace lookup); design ledger updated to 51/51.

---------

Co-authored-by: verify-round <verify-round@local>
Co-authored-by: verify-pr <verify@local>
2026-11-06 18:21:47 +08:00
callmeYe 07a467c38a feat(web-shell): upload Skill archives and reconcile results (4/4) (#16424)
* feat(skills): add file-backed archive installation kernel

* feat(skills): expose native archive routes and receipts

* feat(sdk): support native Skill archives and receipt lookup

* feat(web-shell): upload Skill archives and reconcile results

* fix(web-shell): retain archive results across owner and dialog changes

* fix(web-shell): allow abandoning an unconfirmed archive lookup
2026-11-06 09:51:04 +00:00
chencheng (云谦) a1d713e747 feat(cli): report interactive program status with OSC 7501 (#16407)
* feat(cli): report interactive program status with OSC 7501

Report a single root OSC 7501 record from the interactive UI so terminals
and agent inboxes can show whether Qwen Code is working, waiting on the
user, or finished, without parsing the screen.

- idle / working / blocked (permission, question, auth) / done / error,
  with app=qwen-code on every report
- done and error latch until the next turn; a cancelled turn returns to
  idle; done and error are kept on exit, everything else is cleared
- msg names the tool at most, never its arguments or model text
- TTY only, deduplicated, retried after a failed write; never wrapped for
  tmux passthrough
- cleared while an external editor owns the terminal, rewritten on return
  and on SIGCONT

Closes #13870

* fix(cli): latch the OSC 7501 turn outcome when the turn ends

The outcome was recomputed from a history index on every history change,
so an error added while idle (a failed slash command) flipped `done` to
`error`, and memory compaction reset it to `idle` or hid an error from
the turn. Decide it once when streaming state returns to Idle, bound the
turn by item id, and reset only when history is cleared.

Also count a blocked prompt and a stop-hook loop as failed turns, and pin
the msg-only report, release/restore and editor call sites with tests.

* fix(cli): reset the OSC 7501 outcome on /clear and cap msg by decoded size

/clear only masked the latched outcome: the tracker kept `done` or
`error`, so the next item added while idle brought it back, and exit
could keep a stale badge. Reset the tracker when history empties.

The msg limit checked only the encoded length, which lets a 2049-byte
message through as 2732 base64 characters. Cap the decoded text at 2048
bytes, which also bounds the encoded form.

Correct the design doc: a slash command runs as a turn and decides its
own outcome; it is not an idle history change.

* test(cli): assert the decoded msg is present before measuring it

decodeMsg returns string | null, which tsc --build rejects as a
Buffer.byteLength argument.

* fix(cli): ignore errors already pending when an OSC 7501 turn starts

A terminal API error stays in the pending area until the next submit
commits it, and a Goal submission skips that commit. The next Goal turn
then ended with the old error still pending and latched `error`, which
exit kept. Record the failure items pending at turn start and count only
new ones.

Also restore the program status before the raw-mode restore in
useLaunchEditor, which can throw; say control characters become spaces
in the msg encoder; and note in the design that the OpenTUI renderer
does not report status yet.
2026-11-06 17:34:40 +08:00
jinyeandQwen-Coder 32d22f950c feat(managed-agent): support durable structured user questions (#16433)
Connect Hosted question actions, structured answers, durable continuation, and the WebShell question form. Cover canonical response replay and pending-question recovery across Harness and Java context restarts.

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-06 09:23:44 +00:00
易良andQwen-Coder 1a20792cfa ci: route the no-AK integration gate to ECS for every pull request (#16132)
* ci: route the no-AK integration gate to ECS for every pull request

Same trust class as lint_and_static (2026-11-02 extension of the
2026-10-31 decision): the integration step executes PR code, but the
lane carries zero secrets by construction — every model key is emptied
at the step and the job's only token is the read-only checkout grant.
The gate's step-level runner.temp HOME becomes run-unique and is
cleared before use (the R2-1 lesson: RUNNER_TEMP survives across jobs
on this pool). test and integration_cli keep the association-gated
ubuntu_runner. Mutation-verified: runs-on revert, fixed HOME, and a
real key in the PR-code step each red the routing battery.

* fix(ci): isolate no-AK HOME from the first step; value-pinned zero-secrets

Round-2 review findings: (1) Critical — the gate-scoped HOME missed the
lane's actual fork-code start: checkout's local action and the install
(lifecycle scripts plus scripts/prepare.js, un-skipped) ran under the
runner's real shared home with a shared COREPACK_HOME — the exact
cross-job poisoning channel the lint acceptance closed. Mirror lint:
first-step GITHUB_ENV run-unique HOME + COREPACK_HOME inside it +
shared verified XDG cache + always() reclamation. (2) Critical — the
zero-secrets guard was name-based and missed odd-named secrets; enforce
by VALUE (every non-path env key empty, no secrets.* anywhere in the
gate). (3) runs-on literal pin tightened (timeout line also mentions
lint_runner). Docs: EN single-lane phrasing, stale merge-queue claim,
2026-11-02 paragraph rewritten for the first-step shape. Six mutations
red (first-step removal, shared corepack, odd-name secret, no cleanup,
runs-on revert, fixed HOME).

* docs(ci): correct the enforcement-order claim and stale trust-basis note

Round-2 review: the doc claimed the routing battery runs before
dependency install — false (HELPER_TESTS imports yaml, runs after
install, and not at all on github_ci_only PRs' own runs); and the
ci.yml trust-basis comment still described the round-1 gate-scoped
HOME shape. State the actual timing and detection latency, and the
shipped first-step shape.

* fix(ci): scrub ANTHROPIC_AUTH_TOKEN at the no-AK gate; honest cache naming

Round-3 review: the Anthropic SDK's auth resolver back-fills
ANTHROPIC_AUTH_TOKEN from env, so a runner-account residue would survive
into fork PR code on the pool — add it to the gate's scrub list (pinned
by both suites). The 'Configure persistent npm cache' step name promised
persistence the per-run HOME removes; rename it and record the accepted
cold-install cost (shared pnpm store stays a follow-up).

* fix(ci): scrub ANTHROPIC_AUTH_TOKEN at the no-AK gate; honest cache naming

Round-3 review: the Anthropic SDK's auth resolver back-fills
ANTHROPIC_AUTH_TOKEN from env, so a runner-account residue would survive
into fork PR code on the pool — add it to the gate's scrub list. The
value whitelist only iterates present keys, so the deletion mutation
escaped it; pin the key's presence-and-empty in the battery and the
28-key literal list. The 'Configure persistent npm cache' step name
promised persistence the per-run HOME removes; rename and record the
accepted cold-install cost (shared pnpm store stays a follow-up).

* test(ci): pin the no-AK grant, HOME template and reclamation path

Three assertions in the no-AK routing battery proved less than they read:

- the job's `permissions` was never inspected, so the read-only-checkout
  grant that justifies routing untrusted fork code onto the pool could be
  widened without a red test;
- `create.includes('HOME=')` is satisfied by `COREPACK_HOME=` and
  `XDG_CACHE_HOME=`, so deleting the real HOME/USERPROFILE publication kept
  the test green;
- the cleanup only had to contain the `qwen-no-ak-home-` prefix, so a path
  that dropped `-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}` would still pass
  while removing a directory nothing used.

Pin the grant by value, the publication by its printf template, and the
reclamation by the exact run-unique path the create step builds.

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

* test(ci): pin the realized HOME values in the shared ungated lane loop

R2-1: assert the create `home=` line, the gate QWEN_HOME and the cleanup
command by value, and require the exact set of `$GITHUB_ENV` writers. An
`echo` stand-in for the cleanup, a `-v2` create path and an appended second
HOME writer all kept the battery green under containment checks.

R1-2 / R1-1: hoist those pins into the shared `ungated` loop so
`lint_and_static` gets them too. Its create step was pinned by
`includes('HOME=')` (satisfied by `COREPACK_HOME=`) and its cleanup by
`includes('qwen-')`, so a dropped `HOME=%s` or a retargeted `rm -rf` survived.

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

* test(ci): red on later republishes of the isolated HOME keys

R3-1: the GITHUB_ENV equality guard read step 0 only, so a later step that
republished HOME reverted the run-unique home by last-write-wins while every
assertion stayed green — the isolation would silently stop applying to the
steps that run PR code. Scan the remaining steps for isolated-key
$GITHUB_ENV writers and for step-level HOME/USERPROFILE/COREPACK_HOME/
XDG_CACHE_HOME overrides, leaving the npm cache step's own key a legitimate
later write.

Mutation-verified on this head: appending printf 'HOME=%s\n' ... to
integration_no_ak's 'Configure per-run npm cache (self-hosted)' step and
adding a step-level `env: HOME` each turn the suite red (103 pass -> 102
pass / 1 fail); reverting restores 103/103.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-06 16:41:48 +08:00
jinyeandQwen-Coder 738c4bbf7e fix(serve): bound command snapshot replay memory (#16413)
* fix(serve): bound command snapshot replay memory

Suppress unchanged command snapshots and retain only the latest full skill details so periodic refreshes cannot grow daemon heap indefinitely. Bound replay rings by serialized bytes while preserving ACP live delivery and reconnect IDs, and expose retention diagnostics.

* fix(serve): avoid redundant replay seed serialization

Size only retained complete command snapshots when seeding replay history, and forward the replay byte limit through the development daemon launcher. Pin snapshot accounting and lifetime eviction counters with independent regression assertions, and document the count/byte retention bounds and conditional command notifications.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-06 16:13:17 +08:00
2666e3552d fix(core): make persisted session pagination safe for equal mtimes (#16024)
* fix(core): make persisted session pagination safe for equal mtimes

listSessions() ordered files by mtime alone and resumed the next page with a
strict `mtime < cursor`. An mtime is not unique — bulk copies, backup restores
and coarse-timestamp filesystems all produce ties — so a group of files sharing
an mtime that straddled a page boundary was dropped while hasMore still reported
progress, then the next page came back empty: 25 files at one mtime with size 5
showed 5 items in total.

Sort on (mtime desc, name asc) so the order is total, and let the cursor carry
that position: nextCursor is now `"<mtime>|<name>"`. A bare number is still
accepted with its existing strict `<` meaning, so callers holding a legacy
cursor keep working, and the daemon's opaque string cursor now passes the
composite form through instead of rejecting it as invalid.

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

* fix(cli): carry the composite session cursor type through callers

listSessions() now hands back a "<mtime>|<name>" cursor so a page boundary
inside a group of equal mtimes does not drop sessions. The widened
`number | string` contract left the CLI callers that store or compare that
cursor typed as `number`, which is a type error in `tsc --build` even though
nothing changes at runtime.

Widen the three consumers to `number | string`: the daemon's persisted scan
cursor, the live coordinator's paging plus its loop guard, and the session
picker state used by both the ink and opentui pickers. No behaviour change.

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

* fix(cli): route composite session cursors to the persisted listing

The persisted listing mints `"<mtime>|<name>"` cursors when a page ends
inside a group of files sharing an mtime, but the route still chose between
the persisted and the live listing path with a numeric-only predicate. A
composite cursor therefore fell through to the live path, whose parser only
accepts numbers, and the second page answered 400.

Export one predicate from session-list.ts carrying the same shape test the
cursor parser already applies, and use it for that routing decision.

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

* fix(session-list): keep composite cursors usable by ACP and VS Code clients

listSessions mints "<mtime>|<name>" cursors whenever a page ends inside a
group of files sharing an mtime, but two consumers still parsed every cursor
as a number: ACP session/list rejected page 2 with "not a valid numeric
cursor", and the VS Code companion turned the composite cursor into NaN so
hasMore read false and only page 1 was ever listed. A malformed "|name"
cursor also slipped through the persisted-cursor classifier and answered 200
with an empty page instead of invalid_cursor.

- acpAgent: accept and forward "<mtime>|<name>" cursors to listSessions
- session-list: require a non-empty mtime prefix before classifying a
  cursor as persisted
- vscode companion: pass the daemon cursor back verbatim, widen the cursor
  type and treat only a non-empty cursor as "append to the current page"

Tests: ACP composite passthrough + empty-prefix rejection, VS Code
getSessionListPaged composite nextCursor round trip.

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

* fix(vscode): keep composite session cursors type-safe in the companion

021698420c widened the companion's paged session cursor to number|string but
left AcpConnection.listSessions typed number-only, so the package build failed:
TS2345 at qwenAgentManager.ts:573 and TS2365 at :626.

- accept the daemon's "<mtime>|<name>" cursor in AcpConnection.listSessions,
  which already forwards String(cursor) to ACP
- derive the mtime floor from the cursor prefix in the file-system fallback;
  the previous comparison coerced the composite string to NaN and silently
  returned an empty page

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

* fix(session): align persisted-cursor docs, error kind, and tie-break test

- test(core): feed readdir a non-name order so deleting the sort tie-break
  fails the equal-mtime pagination test (mutation-verified: 9/25 without it)
- fix(cli): report a persisted cursor as such instead of "numeric" in
  InvalidCursorError, matching the message ACP already uses
- docs(cli): describe the opaque persisted cursor in the serve protocol doc
- docs(core): drop the mtime-only-cursor rationale this change removed

Declined: folding the cursor codec into one validated helper across core and
the transports — a cross-package API change outside this PR's scope.

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

* docs(session): describe composite persisted cursors on every continuation page

`listSessions` mints `"<mtime>|<name>"` whenever a page has more results, so
the protocol text must not suggest a bare numeric response. The standalone
list route dispatches through the metadata parser, so its invalid-cursor test
should pin the metadata-kind message instead of the persisted one.

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

* test(session): pin the composite persisted cursor shape and narrow its docs

R3-1: the cursor-contract sentence claimed the numeric-cursor input and the
"<mtime>|<name>" reply for every persisted-backed list, but view=organized and
the parentSessionId / sourceType filtered lists page through their own opaque
activity cursors and reject a numeric cursor with 400 invalid_cursor. Narrow
the subject to the legacy recent list and name the other cursor families.

R3-2: no assertion pinned the documented shape. Every persisted-path cursor
assertion was shape-agnostic, so a regression back to a bare numeric mtime
would have kept the suite green. Assert the shape on a page whose boundary
mtime is unique; mutating the mint site to String(lastProcessedMtime) turns
this assertion red.

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

* docs(daemon): name the persisted list cursor in the SDK catalog guide

The batch-cursor sentence kept the legacy "numeric list cursors" wording while
the list route now mints the opaque `<mtime>|<name>` token, so an SDK
integrator parsing the returned cursor got `NaN` and a cursor they could not
send back. Match the protocol doc and point at the persisted list cursors.

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

---------

Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-11-06 15:43:21 +08:00
45f71b3714 fix(ci): apply the apt-mirror probe to the release docker lane too (#16278)
* fix(ci): apply the apt-mirror probe to the release docker lane too

The release docker integration lane builds the same sandbox image as the E2E
leg but never selected an apt mirror, and it prunes the builder cache on every
run so its apt layers are always cold. On mainland pool hosts that rebuilt
against the throttled deb.debian.org path (11-45 kB/s) — the same defect #16214
fixed for E2E, left unfixed here because it was out of scope (#16271).

Extract the probe (apt_mirror_probe_rate / select_apt_mirror) and a new
apply_apt_mirror helper into a shared .github/scripts/apt-mirror-probe.sh,
sourced by both run-e2e-tests.sh and run-release-docker-integration.sh. Each
calls apply_apt_mirror before build:sandbox, so a mainland host builds against
the VPC intranet mirror (6-9 MB/s, verified for E2E) while a healthy default is
left untouched. The Dockerfile APT_MIRROR ARG already exists from #16218.

Contract tests read the shared probe and pin the candidate loop and the source
statement (not substrings the comments also satisfy); both callers must apply
the probe before the build. Mutation-verified: removing the release call, a
candidate, or a source line each reddens the suite.

* fix(ci): carry the apt-mirror probe into the shard-retry test rig

run-e2e-tests.sh now sources its sibling apt-mirror-probe.sh at startup.
The shard-retry meta-test copies only run-e2e-tests.sh into a temp dir, so
the source resolved to a missing file and 'set -e' aborted before any shard
ran — all eight cases saw zero npm calls. Copy the probe file alongside the
script in the rig so the source resolves; sourcing only defines functions
(no network at source time) and the sandbox:none path never calls
apply_apt_mirror, so the rig stays hermetic.

Witness: CI at 315733fe showed all 8 e2e-shard-retry cases failing
(npmCalls expected 2, got 0); with the probe carried into the rig all 8
pass locally. eslint exit 0, prettier clean.

* fix(ci): address #16278 review — hermetic probe test, shebang, scope header

R1-1: the release-lane exec harness now stubs curl (per-URL rates) so the suite
no longer probes the live network; the npm stub logs BUILD_SANDBOX_FLAGS and two
execution tests assert the fast mirror is passed as --build-arg APT_MIRROR with
the CI label preserved, and that an all-slow probe adds no mirror arg. Flipping
the mirror-selection guard reddens them.

R1-2: add the inert shebang so file(1) reports text/x-shellscript and the
shellcheck lane enrols the sourced probe; a lint test pins the mime type.

R1-3: narrow the header to the two covered build sites and record the third
cold caller (release-sdk.yml build-sdk) as out of scope, tracked in #16317.

* test(ci): drive the shellcheck lane for the probe; copy scripts dir in shard harness

R2-1: the shebang test now runs the lane's own candidate selection with only
shellcheck stubbed, so a filter change dropping the probe is caught (removing the
shebang reddens it). R2-2: the shard-retry harness copies the whole .github/scripts
dir so any sourced sibling comes along instead of a hand-maintained file list.

* merge(ci): sync origin/main to pick up the pinned COREPACK_HOME gate

The lint-gate-freshness lane went red on 9b3d131cdb because main moved
.github/workflows/ci.yml at 2b9c4a98f4, and the shard-4 Linux lane died on
the very corepack 'pnpm.mjs' miss that commit fixes.

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

* docs(ci): stop promising the release lane a benefit its routed host cannot get

`integration_docker` is pinned to `ecs-qwen-hk4-host`, or `ubuntu-latest` when
the ECS pool is unavailable. On the Hong Kong host `deb.debian.org` already
answers at MB/s and the VPC mirror is unreachable, so `select_apt_mirror`
correctly keeps the built-in source; on a hosted runner the same is true. The
call-site comment asserted a mainland benefit this call site cannot obtain, and
the always-cold builder cache it sits next to is unchanged.

The comment now says what the call does: apply the same probe the E2E leg uses,
so a host whose default is throttled builds against a faster endpoint, while a
host whose default is already fast keeps it. The `Fixes #16271` claim is
narrowed to `Refs #16271` in the PR body — the always-cold half stays open.

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

* fix(ci): keep the release docker lane script executable

The previous delivery of 6218478b62 went through the Git Data API, which
recorded .github/scripts/run-release-docker-integration.sh as 100644 while
the base has it 100755. release.yml runs these by bare path, so the mode is
load-bearing, and scripts/tests/release-workflow.test.js
> keeps the extracted release scripts executable already fails on it
(Linux unit tests shard 3/8 on 6218478b62).

Restore the recorded mode to 100755.

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

* test(ci): stage the probe fixture at its real path (#16278 R2-1)

The rewritten case kept its fixture at the temp-repo root, where the
lane's `^([^.]+)` candidate alternative already matches, so deleting the
`.*\.(sh|zsh|bash)` arm left it green. Put the file at
`.github/scripts/apt-mirror-probe.sh` — a leading dot makes the `.sh` arm
the only way it can be selected — teach `setup()` to create parent
directories for nested names, and assert the exact shellcheck argv.

Two-arm check on the lane's own run string: as authored, exit 0 with
`... --color=never .github/scripts/apt-mirror-probe.sh`; with the `.sh`
arm deleted, exit 1 ("matched no shell-script candidates").

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: yiliang114 <[email protected]>
2026-11-06 15:42:59 +08:00
ytahdnand钉萁 255d5cd2ae feat(web-shell): polish composer action menu presentation (#16181)
* feat(web-shell): polish composer action menu presentation

* style(web-shell): use regular composer menu labels

* fix(web-shell): address composer menu review feedback

---------

Co-authored-by: 钉萁 <[email protected]>
2026-11-06 06:47:28 +00:00
Shaojin WenandQwen-Coder 3663209c3d chore(ci): shorten stale PR window to 30 + 14 days (#16435)
* chore(ci): shorten stale PR window to 30 + 14 days

The open PR backlog keeps growing, and the 60 + 30 day window only reaches PRs inactive for two months, leaving the 30-60 day band untouched. Lower the stale threshold to 30 days and the close window to 14 days, and raise operations-per-run to 300 so the first sweep can label the backlog in one or two runs. The stale/close messages and the fleet-shepherd design docs are updated to match.

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

* docs: keep the stale-window reference in the design current

Avoid pinning the note to a merge-date guess: name the policy change instead, and reflow the paragraph to the file width.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-06 14:43:07 +08:00
jinye 6fb741642f perf(managed-agent): take tool execution control off the tenant guards (#16415)
Every tool call admits an execution and authorizes its dispatch. Both went
through lockBindingControl, which for a session-class binding (the default
isolation class) took tenant placement, the publication guard, every slot and
binding row of the Session and then its root, only to prove the Session is
not CSI-pinned.

lockExecutionControl now decides that under the Session root alone: the pin
lives on the root, and profile slots and bindings require a committed pin.
lockManagedSession and requireProvisionPin lock the root before slot and
binding rows, so every placement owner shares the root-then-binding order
that guard-free dispatch uses. Without an existing root, a locking read takes
only a gap lock and orders nothing; dispatch then keeps the guarded path (a
MySQL stress test reproduced the deadlock between the binding's unique-index
and primary-key lookups otherwise). Admission still takes placement for the
harness drain fence.
2026-11-06 06:25:42 +00:00
callmeYe 1bac51b7d9 feat(sdk): support native Skill archives and receipt lookup (3/4) (#16423)
* feat(skills): add file-backed archive installation kernel

* feat(skills): expose native archive routes and receipts

* feat(sdk): support native Skill archives and receipt lookup
2026-11-06 04:48:50 +00:00
16794c25ad chore(release): sdk-typescript v0.1.28 (#16412)
* chore(release): v0.26.8

* chore(release): sdk-typescript v0.1.28

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Shaojin Wen <[email protected]>
2026-11-06 12:26:37 +08:00
callmeYe da7af136e5 feat(skills): expose native archive routes and receipts (2/4) (#16422)
* feat(skills): add file-backed archive installation kernel

* feat(skills): expose native archive routes and receipts
2026-11-06 12:09:38 +08:00
callmeYe 17ad9cdb04 feat(skills): add file-backed archive installation kernel (#16421) 2026-11-06 11:30:04 +08:00
callmeYe 9b1ce46d77 feat(serve): add atomic user Skill activation (#16186)
* feat(serve): add atomic user Skill activation

* test(serve): cover conditional user Skill activation capability

* docs(serve): index user Skill activation endpoint

* fix(serve): preserve User Skill refresh recovery
2026-11-06 11:18:44 +08:00
qqqys 4cea3a792f feat(cli): have an Ink TUI turn await its shared tool batch (#16385)
With experimental.unifiedAgentLoop on, the stream processor hands a
shared round's tool batch to the round's submitQuery, which awaits it,
its completion step and the round that step sends, once its own stream
and activity ended. While another stream is active, which may be the
stream that sent the round (a response of provider replays alone), the
batch completes on its own as before.

- The Ink executor takes a scheduler hold: the scheduler's completion
  callback returns once the completion step stopped the turn or is
  about to send the next round, as the current path holds it, up to the
  round whose calls need the scheduler.
- A batch whose executed calls history all answered no longer sends its
  replay answers alone.

The design doc describes PR 3b-2b.
2026-11-06 10:31:47 +08:00
Lanre Shittu 7bd78683e6 fix(core): accept images for Llama 3.2 Vision, Llama 4, LLaVA, Pixtral and Mistral Small 3.1 and 3.2 (#16311) 2026-11-06 02:43:29 +08:00
曹潇缤andShaojin Wen ecc64a735a feat(qqbot): deliver proactive loop messages as active group messages (#15635)
* feat(qqbot): deliver proactive loop messages to the group target

* fix(qqbot): fail blocked proactive deliveries and post loop output once

- pushProactive now raises ChannelProactiveDeliveryError when
  sendMessageWithReplyContext reports a blocked outgoing route: the
  blocked send resolves instead of throwing, so a loop delivery that
  never reached the wire was being recorded as delivered. The block's
  disposition is kept so the scheduler can distinguish a permanent
  route failure from a retryable one; ACTIVE_MSG_DISABLED still throws
  unwrapped from the send path.
- QQ's streaming chunk sink is skipped for unattended turns (loop and
  webhook task, identified by the new protected isProactivePromptTurn
  predicate), so the full answer is posted once through pushProactive
  instead of being streamed to the group first and posted again. Base
  chunk bookkeeping, lifecycle events and attended streaming are
  unchanged.
- Tests: permanent and transient route blocks reject the loop and never
  send; streamed chunks of an unattended turn produce exactly one send
  carrying the full response.

* fix(qqbot): keep permanent delivery errors permanent and document proactive scope

- pushProactive rethrows QQ's permanent DeliveryError codes
  (RETRY_EXHAUSTED, ACTIVE_MSG_DISABLED, FALLBACK_FAILED) as
  ChannelProactiveDeliveryError('permanent', ...) so the daemon boundary
  classifies a group that cannot receive active messages as rejected
  instead of retryable. RATE_LIMITED stays transient.
- Drop the threaded-target guard copied verbatim from the base override;
  every entry point already gates on supportsProactiveTarget and QQ never
  produces a thread id, so the copy was unreachable drift.
- Name all four callers in the pushProactive doc comment (loop, webhook
  task, background/named-task response delivery, daemon deliverProactive)
  and state that background responses intentionally go out unanchored now.
- Document QQ in the proactive-delivery capability matrix and in the QQ
  channel guide: active messages, group quota, ACTIVE_MSG_DISABLED and
  the 5-failure auto-disable, no threaded targets, learned chat type
  required.

* feat(qqbot): retire loops for chats that can no longer receive proactive messages

A loop created before a group disabled active messages, or before the bot
was removed from the group, kept firing: each run rebuilt a session, ran
a full unattended agent turn, and could never deliver. Both chat-wide
state changes now retire the chat's loops.

- base: add an optional chat-scoped controller lookup (listForChat) and
  protected disableLoopJobsForChat(chatId), which disables every enabled
  loop in a chat regardless of the creator's exact target; the CLI
  controller implements the lookup over the loop store.
- qqbot: handleGroupDelRobot folds the disabled count into its existing
  cleanup log line; handleGroupMsgToggle disables the group's loops when
  active messages are turned off (both handlers become async and are
  dispatched with an explicit rejection log).
- qqbot: refuse proactive targets for a group whose active-message
  toggle is known to be off (supportsProactiveTarget), so loop creation
  is rejected up front and a firing refuses before burning an agent
  turn; direct messages never consult the group toggle.
- tests: creation refusal via the loop tool, run refusal with the prompt
  never called, removal and toggle-off teardown against the real
  controller, DM exemption, mid-flight toggle classification, and the
  base helper's contract.

* test(qqbot): keep the loop-store integration test honest

- Pin a rendered next-fire ISO timestamp instead of base's 'n/a'
  fallback: the copied controller now delegates nextFireTime the same way
  the production controller does, and the store clock is fixed so the
  value is deterministic.
- Redirect QWEN_HOME at the test's tmpdir before the channel constructor
  runs and assert the channels directory lands inside it, so the test no
  longer creates ~/.qwen/channels on the machine running it.
- Resolve the session through the locally constructed SessionRouter
  instead of reaching the protected ch.router, which was a TS2445 that
  no gate in the repo catches (test files are excluded from tsc).

* fix(qqbot): close the round-2 proactive delivery findings

- Resolve the target chat type through chatTypeFor() instead of
  chatTypeMap, and refuse an explicit group target whose type is unknown,
  so config-declared groups and never-learned groups are gated on the
  same type resolveRoute routes on instead of burning an agent turn that
  cannot deliver.
- Route background/named-task responses through pushProactive
  unconditionally: a toggle-off group must not silently receive them
  through the anchored passive reply path.
- Extract one PERMANENT_DELIVERY_CODES/isPermanentDeliveryError predicate
  and use it at the proactive seam and the five existing classification
  sites, preserving each site's behaviour.
- Fix the two comments that named a scheduler disposition check that does
  not exist (the daemon classifyChannelDeliveryError is the only reader)
  and word the blocked-route error as 'outgoing route unresolved
  (<disposition>)'.
- Include the group openid in group-event failure logs and pin the
  websocket dispatch rejection guard with a corrupt-store test.
- Stop retiring a group's loops when its active-message toggle turns
  off: the toggle is reversible, the pre-flight gate already prevents the
  turn burn, and the scheduler's 5-failure auto-disable retires the job.
  Retirement stays on bot removal, where the gate cannot see the change.
- Pin the permanent/transient arms with FAILED-fallback and RATE_LIMITED
  tests.

* test(cli): pin the chat-scoped loop lookup

listForChat decides which loops chat-wide teardown retires, and the QQ
suites substitute hand-rolled controllers whose filter mirrors it. Test
the real controller over a real ChannelLoopStore seeded with two chats
and two channel names, so dropping the filter (or only its channelName
clause) reds instead of silently retiring unrelated users' loops.

* docs(qqbot): record the proactive delivery contract

- Add the design record in English and Chinese covering the capability
  flip and the four base call sites it activates, the single-delivery
  rule for unattended turns, permanent/transient classification, chat
  retirement on bot removal, and the group active-message toggle
  contract.
- Complete the opt-in adapter list (DingTalk Workspace, Email with its
  proactiveRecipients qualifier) and state QQ's target restrictions and
  the unanchored background-response behaviour.
- Document isProactivePromptTurn in the base hook table and the
  adapter-author proactive delivery section.

---------

Co-authored-by: Shaojin Wen <[email protected]>
2026-11-05 18:28:31 +00:00
Shaojin Wenandverify-round e9a20d6ed7 fix(core): keep session list titles when a transcript is rebuilt (#16299)
* fix(core): keep session list titles when a transcript is rebuilt

The sidebar label came only from the transcript: a custom_title record or
the first user prompt. A transcript recreated without its head (missing
file recreated on the next append, crash truncation, external cleanup) lost
the prompt, so displayName became empty and the Web Shell rendered the
session id prefix.

Persist the list label to <sessionId>.title.json next to the transcript:

- the first user prompt (source "prompt");
- custom titles from ChatRecordingService and SessionService.renameSession
  (source "manual"/"auto").

Reads keep the transcript authoritative and fall back to the sidecar.
Archive, unarchive, delete, and storage relocation move or remove the
sidecar with the session. Web Shell rows now render "Untitled session" /
"未命名会话" instead of an id prefix.

Design: docs/design/session-title-sidecar.md

* fix(core): keep the list-label sidecar best-effort

Write it after the transcript append and swallow its errors, so a partial
Config or an unwritable home can never drop a user message. Update the
deletion-cleanup ownership assertion for the extra title-sidecar check.

* fix(core): align the title sidecar with the list reader

- Seed the sidecar when a transcript is resumed (custom title first, then the
  recorded first-prompt display text) so sessions created before the sidecar
  existed gain one without another prompt.
- Write the sidecar when forkSession commits a titled fork.
- Store the prompt's displayText when present and share the reader's
  200-code-point-plus-ellipsis truncation instead of a UTF-16 slice.
- Pin the intended behaviour with tests for a text-less first prompt, resume
  seeding, and a titled fork; update both design documents.

* fix(core): close the review findings on the title sidecar

- Seed a resumed session with its actual first prompt: track that text outside
  the 20-entry title window and fall back to the record's text part when no
  display projection exists.
- Never replace a persisted label from a later prompt. This also lets an
  image-only first prompt leave the slot open for the next text prompt.
- Truncate and validate the sidecar name by code point so an astral prompt
  cannot store a lone surrogate or a discarded ellipsis.
- Reconcile the title sidecar in both archive/unarchive conflict-resolution
  branches, where the hand-rolled sidecar set previously left it orphaned.
- Update the workflow-mode e2e locator for the untitled row label, and cover the
  primary and fanout rows, archive moves, and storage relocation.
- Correct both design documents: the list reads the sidecar when either the
  title or the prompt is missing, and a wholesale chats-directory clear also
  removes the sidecar.

* fix(core): keep sidecar-backed session names consistent

Review round follow-ups for the session title sidecar:

- wrap the archive/unarchive conflict reconcile so a sidecar IO error no
  longer reports an already-completed transcript move as failed;
- also fall back to a manual/auto sidecar in findSessionsByTitle, the
  picker prefix scan, and the unarchive collision guard, so a name the
  sidebar shows can be found with --resume and cannot collide unnoticed;
- converge the live write and the resume seed on one label policy that
  mirrors the list reader (explicitly empty projection, Mod prompts);
- stop a projector-restored session from being labelled with its first
  post-resume prompt, which is not the session's first prompt;
- copy the source sidecar into a fork made without an explicit title;
- return no name instead of another project's prompt text when the
  transcript is refused for project membership;
- keep the id in the fan-out tooltip and the read-only row's accessible
  name, where it is not the visible text.

Tests cover each fix, including the archived half of the lifecycle and a
writer/reader pair on one filesystem; design docs updated in both
languages.

---------

Co-authored-by: verify-round <verify-round@local>
2026-11-06 02:24:26 +08:00
jinyeandClaude Opus 5.5 c4f9899eea perf(managed-agent): take native journal snapshots off the tenant guards (#16410)
lockSnapshotWriter took tenant placement and the publication guard on each of
its three calls per snapshot, so every snapshot of a tenant queued behind
binding transitions, provisioning, retention, maintenance, collectors and
writer acquisition. It now uses the guard-free publication writer.

The build transaction cannot lock the Session root first, because its
nested journal reads take read leases on that root and head. When it reaches
the root it holds only the snapshot's own new rows. Any transaction that
could hold the root and wait for those rows already closes a cycle with it
today through the guards, so dropping them adds no cycle. Root and head
order the writer checks, and the read lease keeps the copied resources. A
pinned CSI Session still takes both guards behind its root.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-05 18:09:10 +00:00
Shaojin WenandQwen-Coder 36c8b34222 perf(core): load the MCP client's Google auth providers on demand (#16242)
* perf(core): load the MCP client's Google auth providers on demand

mcp-client imported GoogleCredentialProvider and
ServiceAccountImpersonationProvider statically, so every process that
loads the MCP client, including each resident Managed Runtime worker,
also loaded google-auth-library whether or not any server uses Google
authentication. createTransport is already async; import each provider
only for a server configured with it.

Add transport tests for the service-account impersonation provider,
which had none in this file. On Linux x86_64 an idle worker loads
0.24 MiB less JavaScript and holds 0.9 MiB less private memory.

* fix(core): ask for a restart when a Google auth provider chunk is missing

Now that the MCP client imports its Google auth providers on demand, the
bundle puts them in separate chunks. A background update that replaces
dist under a long-lived process made the next connection fail with
"Cannot find module", wrapped as a connection error for the server.

Move the restart guidance that contentGenerator already gives for its lazy
provider chunks into utils/errors (getModuleNotFoundError,
wrapLazyModuleLoadError) and apply it to both provider imports. The
original error stays as the cause.

Add google-auth-library to the ACP static closure guard, so a static
import of either provider fails check-serve-fast-path-bundle again.

* test(core): isolate the lazy Google auth chunk tests in their own file

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-05 18:08:58 +00:00
Shaojin Wen a32646e640 docs(design): propose PR state transitions from the daemon PR refresh (#16246)
* docs(design): propose PR state transitions from the daemon PR refresh

The daemon's PR refresh already sweeps every bound PR with the host's
gh credential; the GitHub channel polls the same PRs again to detect CI
failures, merge conflicts, head changes and merged/closed transitions
for bound sessions, with its own credential, schedule, CI definition
and state machinery, and only where such a channel is configured.

Propose observing head, CI and mergeability in the refresh, keeping the
snapshot and pending transitions in the PR sidecar, delivering each
transition into the bound session through the existing prompt admission
and resume paths, and removing the channel watch in a second PR.

* docs(design): keep per-head CI history and daemon-wide recipients for PR state events

Two inconsistencies with the channel watch the proposal replaces:

- A single ciFiredSha lost earlier heads, so a force-push back to a head
  whose CI failure had already fired would fire it again. The sidecar
  now keeps a bounded ciFired ring (16 heads per PR), the daemon's
  equivalent of the channel's durable fired ring, and the acceptance
  criteria pin the revisit case.
- Recipient selection was per workspace, so two registered workspaces
  for one repository would each deliver to their own bound session. The
  sweep that detects a transition now selects one recipient across all
  trusted workspaces, dispatches through that session's runtime, and
  writes the snapshot and fired record to every sidecar bound to the
  PR so sibling sweeps observe no transition.

* docs(design): give prWatchEvents a deprecation window now that v0.26.7 ships it

The proposal justified rejecting prWatchEvents at connect() with "no
tagged release contains the channel watch". That premise is stale:
#16117 merged on 2026-11-03 and v0.26.7 (2026-11-04) ships and
release-notes it. PR B now keeps accepting the field for one minor
release (one warning naming sessionPrWatch, no watch pass), and the
following minor release rejects it; acceptance criterion 3 and the files
table follow. The refresh-interval note no longer claims a one-minute
floor: sub-minute values fall back to the default and 0 disables the
sweep.

* docs(design): reconcile sibling PR snapshots before detection

Propagating a snapshot to every sidecar bound to a PR is a sequence of
separate file writes, so a crash between the recipient's write and the
sibling writes left a sibling one observation behind; its next sweep
re-detected the same transition under a fresh key and the recipient was
admitted twice. Detection now takes the newest sibling snapshot (by a
new observedAt field) as its baseline, so a lagging sibling catches up
silently and the original pending key is the only one for the episode.
The durability section lists this fourth crash window, and the
validation plan and acceptance criterion 5 pin it.

* docs(design): version slim-listing state observations for baseline recovery

The newest-sibling baseline only worked for observations made by
gh pr view: merged and closed states come from the slim listing and
left observedAt unchanged, so after a crash between the recipient's
write and the sibling writes the two sidecars carried the same version
and recovery depended on enumeration order. Every observation that
changes the watch block now stamps observedAt, the block carries the
state seen at that version, and the baseline is the whole block of one
observation. The validation plan covers the terminal-state crash case.

* docs(design): PR-scoped pending, provenance-bound admission and sweep budgets

Folds in the findings of the docs-only review on 8032f568bb:

- pending is PR-scoped: written to every active sidecar bound to the PR
  and drained once per key to a recipient resolved at delivery time, so a
  recipient archived or outranked between detection and drain loses
  nothing; a lagging sibling adopts the newer block with its pending
  entries.
- the daemon-origin classification is a trusted in-process parameter of
  the admission call, never a string read from the request body; the
  marker name only enters the strip lists.
- the GitHub view loop reports its own rotation offset, runs under the
  Aone aggregate view budget, isolates per-PR failures, dedupes views
  across workspaces by freshness, and does nothing when the setting is
  unset; the watch block gets its own mutation helper because
  updateSessionPrStates returns early without a state/issues change.
- over-length keys hash like the channel's; a partial scan skips
  detection; siblings are re-resolved for trust and generation before a
  write; the A→B window skips repositories owned by a configured channel
  watch; wakeDormant: false keeps entries until the next live turn; the
  API budget is stated per workspace; the "identical rules" claim carries
  the CI-predicate difference; validation and acceptance cover each.

* docs(design): never re-target an admitted PR event key

With the recipient reselected on every drain, a key admitted to one
session could be admitted again to another when a replica clear failed
and the most recently active binder changed before the retry: the
prompt route's admission ring is keyed by session and key, so it cannot
suppress that. The drain now records admitted keys per PR for the
daemon's lifetime and only retries the cleanup for them; lazy recipient
selection applies to keys not yet admitted. Validation and acceptance
criterion 2 cover the partial-clear/recipient-change case.

* docs(design): one daemon-owned watch file per PR, switch-off rollout, channel prompt hygiene

Fold in the docs-only review of d93c3e70d8, whose findings were only in
the workflow log (1 Critical, 8 Suggestions), and the audit rounds run
on the revision.

- The event prompt applies the channel's bound-prompt hygiene:
  sanitizePromptText on every GitHub-derived line and the 6,000-code-point
  cap with a visible marker; no display text is set.
- PR state lives once per PR in a daemon-owned watch file instead of a
  replica in every binder's sidecar, so no copy can lag, be adopted or
  re-detect; the sidecars are unchanged. One daemon per global qwen dir
  owns the directory through an owner record with the channel service
  pidfile's semantics; non-ENOENT read errors fail closed for the tick.
- PR A ships the machinery behind an internal switch that stays off; PR B
  turns it on and removes the adapter watch in the same change, so no
  build runs both pollers. prWatchEvents and watch:* actions are read as
  aliases for one minor release.
- Views go by URL from a daemon-private directory, only to GitHub hosts,
  with the workspace's environment; closed PRs stay in the rotation where
  the listing does not observe them; a renamed repository is accepted.
- The rotation wraps as the Aone one does; one tick-level view deadline;
  restores run off the tick; the drain runs under the per-PR lock with
  in-flight marks; admission counts as delivery.
- Role-aware actions (#16258) are carried over; the restore-failure
  split matches the channel's Gone set; partial scans make no negative
  decision; the trust chain reuses context.channelPrompt and the admission
  and restore are extracted into shared functions.
- Stated boundaries: non-owner daemons, daemons with different global
  qwen dirs over one runtime base, a second-writer restore of a session
  another process writes live (as with the channel today), the 7-day
  deletion horizon, once-per-name delivery after a rename.
2026-11-06 01:41:35 +08:00
Bob.qwencodeandgithub-actions[bot] 3718b57fa8 chore(release): v0.26.8 (#16409)
* chore(release): v0.26.8

* docs(changelog): sync for v0.26.8

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-11-06 01:02:08 +08:00
1ac0c6e874 test(web-shell): consolidate sidebar suite fixtures on the shared sidebarHarness (#16016)
* test(web-shell): migrate WebShellSidebar.brand fixtures to sidebarHarness

* test(web-shell): migrate WebShellSidebar.footer-version fixtures to sidebarHarness

* test(web-shell): migrate WebShellSidebar.local-files-footer fixtures to sidebarHarness

* test(web-shell): migrate ChatPane fixtures to sidebarHarness

* test(web-shell): migrate WebShellSidebar.collapse-persist fixtures to sidebarHarness

* test(web-shell): migrate WebShellSidebar.current-session-delete fixtures to sidebarHarness

* test(web-shell): migrate WebShellSidebar.session-pinning fixtures to sidebarHarness

* test(web-shell): migrate WebShellSidebar.workspace-removal fixtures to sidebarHarness

* test(web-shell): use sidebarHarness in App.test.tsx

* style(web-shell): format sidebarHarness.ts

* test(web-shell): migrate StandaloneRecents fixtures to sidebarHarness

* fix(web-shell): repair merged sidebar suites (React import, stable controller mock, listWorkspaceSessionsPage, hook naming)

* fix(web-shell): repair gate2 sidebar failures (createSessionGroup, useStreamingState, stable catalog query identity)

---------

Co-authored-by: Сергей Панченко <[email protected]>
Co-authored-by: 易良 <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
2026-11-06 00:59:26 +08:00
Shaojin Wen ad487ffedb feat(autofix-pr): give helper refusals a code and a next step (#16238)
* feat(autofix-pr): give helper refusals a code and a next step

A helper that refuses now exits 1 with one JSON line last on stderr —
{"refused":"<code>","next":"<step>","message":"…"} — instead of throwing
to the CLI's critical-error handler, which printed the error's stack. Each
yargs handler catches the error and prints the line, as review's
extract-step does. Anything that is not a coded refusal is `failed`.

The step depends on the helper as well as the code: `watched_elsewhere` is
`take_over` from `prepare` and `end_task` from a round's helper,
`worktree_dirty` is `discard` from `sync` and `needs_human` from `push`,
and a `reply` that failed is `retry`, as its second run skips what the
first one posted.

The skill acts on `next` through a new "Refusals" section instead of
matching the wording of messages, and the autofix_pr tool returns the coded
line first, verbatim, then what the helper printed before it. Design
section 6.16 lists the codes.

A clone with no directory for the watch's lock (its watch files removed)
is refused as `not_prepared` instead of with ENOENT.

* fix(autofix-pr): code the refusals whose step the caller needs

- A reply plan that is not JSON or not shaped as a plan is `plan_invalid`
  (`fix_plan`), not `failed` (`retry`): retrying it unchanged fails again.
- A switch back that `sync --discard` asked for and git refused is
  `failed`: as `worktree_dirty` it answered `discard`, which asks again.
- The skill's Resume step keys on the code `pr_closed`, since `prepare`
  gives it `report`; `retry` holds wherever a step says a refusal ends it;
  refusals with no coded line are the calling step's to handle.
- The tool takes the last coded line of stderr, not the last line, and a
  refusal's message keeps at most 8,000 characters, its head and tail.
- Each helper's code is pinned through the command in an empty clone.

* fix(autofix-pr): stop a watch git cannot switch back, and fit the coded line

- `sync --discard` that git cannot switch back to the watch's branch
  answers `synced: false`, as a worktree still dirty after discarding
  does, so the round stops instead of reporting the same refusal at every
  fire.
- A reply plan that cannot be read is `plan_invalid` as well.
- A refusal's message drops terminal controls and is cut to 8,000
  characters as JSON, not as text, so the line fits the stderr the tool
  keeps.
- The tool reads a coded line only from a helper that exited 1, and only
  when nothing but Node's warnings follows it.
- The skill's `discard` step tells Round step 1, where it clears what an
  earlier round left, from the push of step 6, where the round's own fix
  left the worktree unclean.

* fix(autofix-pr): answer the first review of the refusal codes

- A switch back git refuses because the branch is checked out in another
  worktree names that worktree and how to free it: `cleanup` cannot
  delete the branch either while it is held.
- A message drops escape sequences whole and the daemon's log-unsafe
  class (C1 controls, bidi overrides, invisible formatters), keeping tabs
  and newlines.
- A defect, not a refusal, prints its stack frames before the coded line.
- The coded line survives the frames `--trace-warnings` adds after it.
- `fix-start` refuses a fix in flight with the message that says how it
  ends.
- The tool's description states the result's prefix, and `partial` is
  described by the deadline that raises it.
- Tests: the whole step matrix as a specification, `cleanup` and `status`
  through the command, the order of the watch checks, prepare's `failed`
  arm, the line within the stderr tail the tool keeps, and the skill's
  steps read from the helpers' source with what each does.

* test(autofix-pr): pin a C1 control a refusal's message drops

* fix(autofix-pr): say how to free a branch a stale or locked worktree holds

When `sync --discard` cannot switch back because another worktree has the
watch's branch, the reason now fits what holds it:

- a worktree whose directory or `.git` was deleted by hand: drop its
  record with `git worktree prune`, since `git worktree remove` refuses
  one whose `.git` is gone;
- a locked worktree: unlock it first, since neither `remove` nor `prune`
  touches a locked one.

The test runs each case's git commands as the reason gives them and
checks that the next round gets through.

The every-helper refusal test now joins its fake `gh` to PATH with
`path.delimiter`, and its `prepare` row refuses before calling gh. The
fake records its calls and the test asserts there are none, because on
Windows `execFile` would never run the script.

* fix(autofix-pr): quote the holder path in the recovery commands

The `git worktree remove` and `git worktree unlock` commands that a
switch-back refusal prints interpolated the holder's path bare. A path
with a space split into two arguments (git exits 129), and a quote broke
the shell, so the branch stayed held. Both commands now quote the path
with `shellQuotePath`, the form review's printed repair commands use.

The holder test now runs every case at a plain path and at one with
spaces and both quotes. It runs the printed commands through `sh -c`
instead of splitting them on spaces.

* fix(autofix-pr): answer the second review of the refusal codes

- The tool runs its helpers with NODE_NO_WARNINGS=1. Node flushes a
  warning raised while a helper refuses after the coded line, and a
  warning's text can run over several lines, which hid the line from
  `codedRefusal`, so the code and its `next` were lost. A new test runs a
  helper that raises such a warning: run directly, the helper prints the
  warning after the line; run through the tool, its line is read.
- The switch-back reason's git commands carry `-C` with the watch's
  worktree, so they run as printed from any directory. A branch held by
  the clone's own checkout gets only "switch that checkout", because git
  never removes the main worktree.
- `worktreeHolding` reads `git worktree list --porcelain -z` (git 2.36+)
  and falls back to the line form on older gits, so a path with a
  newline in it is no longer cut short.
- New tests: the holder matrix also runs at a path with a newline and
  runs its commands from outside the clone; the main-checkout holder;
  and the no-holder arm (an index.lock stops the switch), whose `cleanup
  --force` advice nothing reached before.

* fix(autofix-pr): keep a watch whose branch another checkout has

When `sync --discard` cannot switch back because another checkout has
the watch's branch, it answers `synced: false`, and the skill stops the
watch with `cleanup`. `cleanup` removed the worktree first and then ran
`git branch -D`, which git refuses while that checkout has the branch.
The watch was left with its state file and branch and no worktree, and
every later `cleanup` and `prepare` failed at the same delete.

`cleanup` now checks `git worktree list` before it removes anything,
`--force` or not. When another checkout has the branch, it keeps all
three and ends the watch, as it does for unsaved work. It answers
`removed: false` with `heldBy`, and a reason that says how to free the
branch and to run `cleanup` again. The holder lookup is shared with
`sync` (`branchHolder`) and anchors its commands at the clone root.

The skill's Stop step and both design docs say what `heldBy` means.
2026-11-05 16:53:30 +00:00
Shaojin Wen a7d0193991 fix(core): keep a writer grant an in-flight renewal refreshed (#16287)
* fix(core): keep a writer grant an in-flight renewal refreshed

reacquireWriter() waits for an in-flight writer renewal before it
acquires, then always dropped the grant and posted the acquire. When
that renewal had answered with a live lease, the fresh grant was thrown
away; until the acquire answered, authorizeLifecycle and any caller
reading the grant without waiting in ensureWriter refused locally with
"the HTTP Managed Session writer is not active." A Store holding the
Session's lifecycle can hold that acquire, so a Hosted Harness refused
lifecycle phases during Spring recovery. HostedShellLifecycleRecoveryIT
[spring-kill] failed this way in 17 of 239 CI runs since #15813 made
renewActivation reacquire a lapsed writer.

Keep the grant when the awaited renewal moved the lease past the lapsed
one and the new lease is live. A refused renewal, or a commit that only
copied the lapsed lease, still reacquires.

* test(core): pin the lease check and renewal timer a kept grant relies on

Two cases join the reacquireWriter tests:

- A refused renewal is held while, after a wall-clock step back, a
  journal read copies the lapsed lease into a new grant object. The
  writer must still be re-acquired. Only the check that the lease moved
  forward guarantees this; a check on grant identity would keep the
  copy.
- The grant is kept, then the activation renewal meets the lifecycle
  claim fence. The timer that the kept grant's renewal re-armed must
  still send the next ordinary renewal before that lease ends.

The reverse case now reaches the lapse before the wait, so the live
check has to read the clock after it. The comment in reacquireWriter
names the journal read among the paths that copy a lapsed lease.
2026-11-06 00:51:40 +08:00
11f6163f0f perf(managed-agent): narrow SSH settlement and worktree locking (#16386)
Settle SSH registrations using their operation and exact physical owner.
Allow ordinary Runtime members and uploads during main worktree preparation
while retaining maintenance ownership and cleanup exclusion.

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
2026-11-05 16:26:28 +00:00
Shaojin Wen b3ad01e875 perf(managed-agent): index the activation lease lookup (#16336)
* perf(managed-agent): index the activation lease lookup

Each activation renewal reads the Session's current activation row,
three times a minute for every resident Session. Renewals update that row
in place, so it is as old as the activation, and the lookup walked back
over every journal transaction committed since: 22 rows after one Turn,
717 after 40.

Add a generated is_activation_record flag for the two activation
operations and an index that leads with the Session, its activation
epoch and the flag, and look the row up by the flag. A simple CASE keeps
H2 from compiling the expression into an IN set bound to the session that
created the table. The CSI application image builds its expected journal
row column by column, so it sets the flag the way the database does.

A MySQL integration test pins the lookup's plan and result with 1,000
and 10,000 later transactions.

* test(managed-agent): pin the CSI image's mirror of the activation flag

The CSI application image rebuilds the journal row a cut adds column by
column, so it computes is_activation_record in Java as the generated
column does. Nothing held the two together.

- WorkspaceCsiApplicationImage.ACTIVATION_OPERATIONS names the
  operations the CASE marks, and V1 points back to it.
- ActivationRecordFlagTest parses the journal table's CASE and requires
  exactly those names and ELSE 0, then checks on H2 that the generated
  value matches the mirror for every name the CASE mentions and other
  operations the Session Store commits.
- The CUT-image test asserts the added row carries "false" for the
  cut's releaseActivation.
2026-11-05 16:16:55 +00:00