Commit Graph
11305 Commits
Author SHA1 Message Date
jinyeandQwen-Coder 288e4c481d perf(ci): Optimize MySQL integration verification (#15200)
Close Spring contexts before fixture database cleanup, run integration classes in isolated reusable forks, and remove redundant fixture validation and duplicate load-test execution. Document bounded local validation and preserve full CI gates.

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-22 12:03:13 +00:00
qqqys e9011d21c3 fix(providers): refresh the ModelStudio Standard preset and enable model discovery (#15189)
Discover the models a Standard key actually serves through /models, as the
Token Plan and QwenCloud Standard presets already do, so regional and newer
models appear without a preset change.

Drop deepseek-v4-flash, which the service no longer has, and add
deepseek-v4.1-flash, deepseek-v4.1-flash-1017 (image input), glm-5.3
(thinking-only) and glm-5.2. qwen3.8 models take the full effort ladder up
to max, and glm-5.1/5.2 declare their enable_thinking switch so Off reaches
the wire.

The API key step now hints the pay-as-you-go key format and rejects
subscription (sk-sp-) keys.
2026-10-22 19:51:34 +08:00
qqqys 11e371c537 fix(providers): refresh the Alibaba Token Plan model list and thinking controls (#15184)
* fix(providers): refresh the Alibaba Token Plan model list and thinking controls

Drop qwen3.8-max-preview and deepseek-v3.2, which neither Token Plan
edition serves any more, and add deepseek-v4.1-flash, deepseek-v4.1-flash-1017
and glm-5.3. DeepSeek V4.1 Flash accepts image input.

qwen3.8-max and qwen3.8-flash take the full effort ladder up to max, and
qwen3.8-max can be turned off again. glm-5.2, glm-5.1 and glm-5 declare
their enable_thinking switch so Off reaches the wire; glm-5.3 and
MiniMax-M2.5 are thinking-only.

The description now reflects the subscription Credit quota and both
editions.

* test(core): pin the provider-policy inheritance case to a Token Plan-only model
2026-10-22 11:40:11 +00:00
jinyeandQwen-Coder 51f1d2c8bb fix(managed): Align checkpoint limits and expose recovery blocks (#15094)
Co-authored-by: Qwen-Coder <[email protected]>
2026-10-22 11:37:11 +00:00
易良andQwen-Coder 0b3c2f816a feat(ci): restore a MariaDB leg for the non-Hosted IT family (#15003)
* fix(ci): align Java integration database and lock lifetime

* test(ci): re-pin the sdk-java lock and MySQL probe after the single-step merge

The merge commit staged the conflict resolution before these two re-pins were
written, so they land as a follow-up in the same push:

- `keeps the host lock out of every Maven child` counted three lock-bearing
  steps; #14903 collapsed the MariaDB lane's two Maven steps into one, so the
  count is now two (test job + mysql-integration).
- The MySQL startup probe took the step's first `mvn` line, which is now the
  `-DskipTests install` staging command; select the `-Pmysql-integration`
  command instead so the probe still exercises the consumer.

Verified: vitest sdk-java-workflow.test.js 96 passed; prettier --check clean.

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

* fix(ci): skip the MySQL binlog, drop the unused database, correct the reaper bound

Three independent corrections to the mysql-integration lane this PR reworks.

1. MySQL 8 enables the binary log by default (MariaDB 10.11 did not in the
   configuration this lane replaced), and the datadir is an uncapped tmpfs.
   Nothing in packages/managed-agent-server/src or packages/sdk-java reads a
   binlog (0 hits for binlog|log_bin|GTID|SHOW MASTER STATUS), and the
   container is disposable with no replica, so `--skip-log-bin` is appended
   after the image name beside --innodb-use-native-aio=0. Without it the
   ~67 MiB hex-encoded LONGBLOB query this lane already raises
   max_allowed_packet for is serialized a second time into host RAM.

2. `--env MYSQL_DATABASE='runtime_broker_test'` had no consumer: the lane's
   only connection is -Dmysql.url=.../managed_agent_test?createDatabaseIfNotExist=true.
   The declaration invited a cleanup that drops createDatabaseIfNotExist and
   dies at first connect with Unknown database. Dropping it lets the JDBC URL
   own the schema and shrinks the diff.

3. The cleanup comment and removal-failure warning said the host reaper
   reclaims a stranded container "after a 24h age gate" / "up to a day".
   The real bound is ~48h: qwen-docker-cleanup.sh:31 gates on a strict
   `now - created_at > 86400` (exactly 24h is SKIPPED) and
   qwen-docker-cleanup.timer fires once a day, so a container created just
   after a fire is skipped at the next one and reaped at the one after.

Test pins updated in the same change: the start-step assertion, the exact
docker-run trace literal, and the cleanup warning literal. All three were
mutant-verified red (drop --skip-log-bin / re-add MYSQL_DATABASE / restore
"after 24h") and restored byte-identical.

Only this PR's own copy of the reaper wording is corrected; the two
pre-existing copies (sdk-java.yml's event-transport-multi-node job and
release-managed-agent-reference.yml) are untouched to keep this diff scoped.

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

* feat(ci): restore a MariaDB leg for the non-Hosted IT family

#14936 moved the mysql-integration lane onto MySQL 8.4 and removed the last
real MariaDB server, leaving the published MariaDB-only V12 repair (RTRIM,
error 1901) and the schema catalog's MariaDB branches without a falsifier.
Both engines now run the same non-Hosted *IT family as two matrix legs of one
job: the container image, its startup arguments, its client, and its readiness
signal come from the leg, and the container name carries the engine so two
legs on one pool host cannot collide.

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-22 11:24:34 +00:00
易良andQwen-Coder ecc774ca79 test(core): pin the no-upstream branch shape the web-shell push hint reads (#15073)
Every pushTarget-undefined assertion in git-branches.test.ts carried a live
upstream -- triangular push.default=simple, a name-mismatched upstream,
push.default=nothing, and a Gerrit remote.<name>.push refspec. Nothing pinned
the shape where a branch has neither an upstream nor a resolvable push
destination, which is exactly the one the web-shell push row renders as
"Sets upstream on push" (hasUpstream === false && !pushKnown). A parser
regression that invented an upstream or a push target there would move that
hint without failing a test.

Pin it with a branch pushed without -u: the remote ref exists, git still
resolves no upstream, and %(push) comes back empty. The branch identity is
asserted first so the undefined checks cannot pass vacuously on a listing
that failed to find the branch at all.

Refs #10964

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-22 19:04:31 +08:00
易良andQwen-Coder 013b8055cc test(managed-agent): atomically claim MCP fixture closes (#15158)
* test(managed-agent): atomically claim MCP fixture closes

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

* test(managed-agent): share atomic lifecycle fixture claim

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

* test(managed-agent): remove unused MCP fixture imports

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-22 17:47:08 +08:00
易良andQwen-Coder 6d471f9221 test(managed-agent): pin durable recovery park acceptance (#15154)
Co-authored-by: Qwen-Coder <[email protected]>
2026-10-22 17:45:08 +08:00
ytahdnand钉萁 ba0813ba93 fix(web-shell): avoid page locks in table controls (#15179)
Co-authored-by: 钉萁 <[email protected]>
2026-10-22 17:42:52 +08:00
易良andQwen-Coder b20888e6f8 feat(managed-agent): expose durable capacity observations (#15162)
* feat(managed-agent): report durable capacity facts

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

* fix(managed-agent): clarify capacity observation timeouts

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-22 17:41:53 +08:00
aaec411379 fix(web-shell): show actionable skill installation errors (#15013)
* fix(web-shell): show actionable skill installation errors

* test(cli): synchronize hosted monitor shutdown requests

* test(ci): seed Java lock queue fixture

---------

Co-authored-by: Ethan Ye <[email protected]>
Co-authored-by: claudeethan <[email protected]>
2026-10-22 17:36:15 +08:00
qqqysand衍星 034e35056b feat(generated-ui): expose interaction outcomes and applied parameter snapshots (#15004)
Co-authored-by: 衍星 <[email protected]>
2026-10-22 17:32:47 +08:00
易良andQwen-Coder c4d2eaa9ee test(core): pin that setTools writes the tool declarations (#15118)
* test(core): pin that setTools writes the tool declarations

`setTools` is the only writer of `generationConfig.tools`, but nothing in
`llm-chat.test.ts` reached it: the ACP-level suites only assert that a
*mocked* `setTools` was called, which cannot observe the write. A refactor
that dropped `this.generationConfig.tools = tools` therefore left the whole
suite green while every request went out tool-less.

Verified by mutation on this tree: removing the write keeps 592/592 green,
and with these two tests added exactly the two new cases fail
(`expected undefined to deeply equal [...]`).

Part of #11056

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

* test(core): pin that setTools([]) clears stale declarations

The block pinned the establish side only. Production reaches the empty
leg whenever a tool-restricted registry refresh hands setTools an empty
list, so add the clear-side case: a skip-empty guard in setTools now
fails instead of leaving stale declarations advertised to the model.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-22 17:09:36 +08:00
jinyeandQwen-Coder 605fd5ee17 feat(managed-agent): Add session lifecycle UI and reading exports (#15100)
* feat(managed-agent): Add session lifecycle UI and reading exports

Expose durable lifecycle actions and archive navigation in Managed Web Shell, with identity-scoped browser recovery and immutable HTML/Markdown reading exports.

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

* fix(managed-agent): Resolve lifecycle review and smoke regressions

Use the host React DOM server renderer, announce global errors once, and advance the OpenAPI version. Keep smoke selectors specific to session navigation and align the Workspace expectation with its existing Managed label.

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

* test(managed-agent): Preserve schema bindings in channel race fixture

Reuse the production schema-aware JDBC template so repeated H2 queries keep binary identity parameters. Preserve the existing ownership race and its assertions.

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

* fix(managed-agent): Correct lifecycle retry and export handling

Keep failed unadmitted archive intents idle until explicit retry, scope approval capability to each session profile, and preserve public export semantics and embedded downloads.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-22 16:37:26 +08:00
Shaojin Wenand易良 ec82cac361 test(managed-agent): keep a sibling run alive past a cancel (FG11d) (#14999)
* test(managed-agent): keep a settled run's outcome (FG11d)

* test(managed-agent): reuse a same-key cancel (FG11d)

* test(managed-agent): refuse a second fire while the turn runs (FG11d)

* revert: move the second-fire cell onto its own branch

The cell belongs to the next PR in the chain, which bases on this branch, so
it cannot also live here: the next PR's diff would be empty. Content of this
branch is unchanged from acdab642b7.

* test(managed-agent): refuse a second fire while the turn runs (FG11d)

* test(managed-agent): keep a sibling run alive past a cancel (FG11d)

* docs(managed-agent): name the settled run's real refusal code

The cell's own comment still said task_already_settled while the assertions
and both design docs record 409 task_action_unavailable, which is the code the
measured behaviour actually answers.

* test(managed-agent): assert the replayed cancel operation's identity

The cell discarded both cancel responses, so "reuses its operation" rested on
the run state alone — which a second operation could also have produced. It now
compares the two operation ids, pins task_id to the cancelled task, and asserts
the replay flag flips false then true.

* docs(managed-agent): separate the queued-input claim from the settled one

The bullet ran the two claims together with no separator, so a reader could not
tell whether "a queued input cancelled before any attempt" was delivered too.
Both language versions now take the same form as the neighbouring bullets, and
the Chinese continuation line gets its indentation back.

* test(managed-agent): pin the occurrence ledger in the isolation cell

Review R1-1 and R1-2: the cell's comment claimed the schedule's future fires
survive, which nothing in it observes, and it was the only cancel cell that
never read ma_schedule_occurrence. The comment now says what the cell proves
(the definition stays live; a scheduled slot is outside the arm) and the cell
pins both keys fired exactly once with no re-drive. The workflow comment's
wrap figure follows the fork to 11781.

* docs(managed-agent): leave the queued-input arm open and fix its carriers

Review round 2. R1-1: the queued-input criterion is reachable, so the bullet
no longer books it delivered — under queue_one (and allow below quota) a second
manual fire is admitted while the first run is active, because hasOpenTurn reads
only public ma_turn rows. The cell pins the default-skip refusal, not that arm.
R1-10: the cell's comment now names the count arm instead of the open-Turn arm.
R2-1/R2-2: the workflow's two budget comments and both derivation ledgers record
this cell's 25 s (fork 11212, healthy 10612).

* test(managed-agent): catch a re-drive by attempts, not by row count

Review R1-2 on this PR: the count assertion cannot witness a re-drive, because
the occurrence primary key makes a re-drive an UPDATE that bumps attempts rather
than an INSERT. Both keys now pin attempts == 0, and the count assertion is
labelled for what it does catch - a spurious third key. Re-ran the cell on
JDK 21 + MySQL 8.0.46: 6.204 s, green.

---------

Co-authored-by: 易良 <[email protected]>
2026-10-22 16:17:56 +08:00
qwen-code-dev-bot 9d1e41f55c test(sdk-java): close the body-less pre-contract 404 connection (#15167) (#15172)
The fixture answered the first prompt with a body-less 404 and no
Connection: close. On Java 11 the JDK server drops that connection while
the HttpClient keeps it pooled, so the follow-up submission sometimes
read EOF and surfaced as an outcome-unknown admission instead of the
expected receipt.

Mark the response non-persistent like the other body-less fixtures. A
standalone Java 11 probe failed 21/500 rounds without the header and
0/500 with it.
2026-10-22 15:42:46 +08:00
240eecf617 feat(web-shell): allow hosts to inject disabled catalog models (#15015)
* feat(web-shell): allow hosts to inject disabled catalog models

Hosts can show product models that are not currently usable in the composer picker, with a framework-neutral hover slot for text or authorize actions.

Co-authored-by: Cursor <[email protected]>

* docs(web-shell): add unavailable-model picker screenshots

Co-authored-by: Cursor <[email protected]>

* fix(web-shell): polish unavailable-model tooltip card

Co-authored-by: Cursor <[email protected]>

* fix(web-shell): make unavailable models look disabled

Co-authored-by: Cursor <[email protected]>

* fix(ci): isolate script fixtures and reuse runtime archives

* test(cli): bound command budget fixture work and drain timed-out writes

---------

Co-authored-by: Ethan Ye <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-10-22 07:09:59 +00:00
易良andQwen-Coder 54a56f30ce fix(ci): write back only the MariaDB lane's Maven delta and size its ceiling (#15000) (#15064)
* fix(ci): bound the MariaDB lane's cache write-back and size its ceiling (#15000)

Run 39563394993's mysql-integration job was cancelled at the 200-minute ceiling: 54 minutes queued for the host lock, 75 minutes of Maven work, then a 60-minute unbounded tar of the host-shared ~/.m2. The cache is an optimization, so skip it after five minutes; the ceiling now covers the 90-minute wait, the measured Maven pass, the bounded write-back and setup headroom.

* fix(ci): write back only the MariaDB lane's Maven delta by hardlink

The bounded tar pipeline extracted straight into the host-shared ~/.m2,
so a timeout mid-member left a truncated artifact that --skip-old-files
never repaired and the next seed hardlinked into its job. Its 300 s cap
was also below the measured 26-61 minute whole-tree cost, so it would
have tripped on every run.

Publish only the files Maven fetched in this job (nlink == 1 after the
hardlink seed) with per-file cp -ln, keeping the two in-house excludes.
Each link is atomic and never replaces a host file, so the 300 s
backstop can only leave artifacts missing. Distinguish the timeout from
other failures, and fold the backstop into the derived ceiling floor
instead of a second hardcoded one.

* ci: record sdk-java.yml's real size in the workflow ratchet

This PR grows .github/workflows/sdk-java.yml from 113473 to 114180 bytes:
the MariaDB lane's timeout ceiling moves 200 -> 280 with the arithmetic
that justifies it, and the Maven write-back is rewritten to publish only
this job's own downloads (nlink == 1) under a 300 s backstop instead of
taring the whole tree. The growth is the change, so record it rather than
move the step out.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-22 14:48:11 +08:00
易良andQwen-Coder d6abebb40c fix(ci): report swap and a df -h fallback in the lint lane failure dump (#15095)
* fix(ci): report swap and a df -h fallback in the lint lane's failure dump

The lint lane's state-at-failure dump asked /proc/meminfo for MemTotal and
MemAvailable only, and ran df -hT with no retry, while the three sibling dumps
in the same workflow grep SwapTotal|SwapFree and fall back to df -h. A
swap-exhausted death on the contended pool therefore uploaded an artifact that
could not show it, and a runner whose df has no -T uploaded no filesystem table
at all.

Witness it by executing the step body rather than pinning its text: the new case
stubs a df that rejects any option cluster carrying T, so the marker can only
come from the -h retry, and asserts both swap fields reach the artifact. Red
before (the artifact held MemTotal/MemAvailable only and no filesystem table),
green after.

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

* test(ci): pin the retained dump fields and the three sibling dumps

R1-1: the swap/df-fallback case asserted only what the PR adds, so the
halves of the two edited lines it kept were unguarded. Collapsing the
retry to `df -h || true`, or reordering it to `df -h || df -hT || true`,
stayed green while the artifact permanently lost the filesystem Type
column; narrowing the grep to swap alone stayed green while the lane this
PR fixes stopped reporting RAM at failure. Give the stub's `-hT` arm its
own marker so "retried after -hT failed" is distinguishable from "never
tried -hT", and assert both RAM fields beside the swap ones.

R1-2: the same dump is replicated at four sites and nothing pinned them
equal, so dropping the swap fields or the `|| df -h` retry from test,
test_macos or test_windows shipped green. Pin the three siblings
textually, extending the sampler pin loop idiom already in this file --
textual rather than executed because test_windows greps /proc/meminfo,
which Git Bash lacks.

Test-only: .github/workflows/ci.yml is byte-unchanged by this commit
(md5 31b18cbbaeb407d9348e1f3f51f1c0b4). Verified with a nine-mutant
battery run one mutant at a time against ci.yml, restoring and md5-
checking after each: all nine are now killed, each by a named assertion.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-22 14:44:05 +08:00
易良andQwen-Coder 3efcf2adf7 docs(managed-agent): propose local-process confinement (#15163)
* docs(managed-agent): propose local-process confinement

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

* docs(managed-agent): clarify confinement profile scope

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-22 14:38:02 +08:00
jinyeandQwen-Coder 59d21e1c69 feat(managed-agent): Safely settle recovery and continue sessions (#15131)
* feat(managed-agent): Safely settle recovery and continue sessions

Require original-input settlement, continuation readiness and completed cleanup before reopening a Managed Session. Add conservative recovery queries and rechecks, original-target cancellation, reload-safe operation tracking and editable bounded handoff drafts.

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

* test(managed-agent): Avoid initial fixture output backpressure

Capture the native generator output in a JUnit temporary file so waiting for its exit cannot fill an unread pipe. Preserve the 30-second deadline and all recovery assertions, and reap the generator when an assertion or interruption ends the wait.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-22 06:06:57 +00:00
qqqys 15f3226db5 fix(core): align official DeepSeek with deepseek-flash vision and effort tiers (#15164)
* fix(core): align official DeepSeek with deepseek-flash vision and effort tiers

The DeepSeek preset now offers deepseek-flash, the official V4.1 Flash name,
instead of the retired deepseek-v4-flash alias. deepseek-flash accepts image
input, so images are no longer replaced with an unsupported placeholder.

On the official Anthropic-compatible endpoint, low now passes through and
xhigh maps to high, matching DeepSeek's server-side mapping.

* docs(core): clarify which DeepSeek models the flash vision and effort notes cover
2026-10-22 05:23:50 +00:00
qqqys e138753be0 fix(core): link workflow agents to the Workflow tool call that started them (#15145)
Workflow agent() subagents were named workflow-agent-<random hex>, so the
trajectory view could not resolve their spawning call and flagged every
workflow request as "Parent call not located". Suffix the Workflow tool
call id the way the Agent tool already does.

Refs #15140
2026-10-22 05:22:59 +00:00
438bcb2a8f fix(ci): let the fast Java tier overtake waiting full-tier lock tickets (#15035)
* fix(ci): serve the fast Java tier before full-tier lock holds (#15032)

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

* fix(ci): keep a parked lock head's ticket alive under fast priority

A head blocked in `flock --wait` could not touch its ticket, so once a
hold outlasted the 1800s mtime arm another waiter reaped a live head and
the next ticket, whatever its class, parked in the kernel too. With two
classes that lets normal tickets take kernel positions ahead of fast
tickets that arrive later during a long hold. A keepalive now refreshes
the parked head's ticket at the waiters' two-second cadence; it closes
the lock fd and uses `touch -c`, so it can neither hold the lock nor
recreate a removed ticket.

The ordering and promotion cases used a stub whose `--wait` returned
every 200 ms, so they exercised a head that loops, which production never
does. They now park `--wait` until release and assert the reachable
paths: a fast ticket overtaking a normal ticket that is still waiting,
and a promoted waiting normal ticket preceding a later fast ticket.

The comments and design docs no longer claim a fast job waits at most
the hold already running (a parked head still goes first) or cite
#14962/#15032 as fast-behind-full evidence; they state what priority
removes and that a single hold longer than the budget is outside it.

---------

Co-authored-by: Qwen Autofix <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: wenshao <[email protected]>
2026-10-22 03:54:27 +00:00
ytahdnand钉萁 952c6de739 fix(core): remove duplicate prompt metadata import in chat test (#15146)
Co-authored-by: 钉萁 <[email protected]>
2026-10-22 11:25:23 +08:00
易良 7091697f00 fix(ci): enroll the OpenTUI parity tests in the no-AK lane (#15142) 2026-10-22 11:16:42 +08:00
Shaojin Wenand易良 f773580e13 test(managed-agent): refuse a second fire while the turn runs (FG11d) (#14998)
* test(managed-agent): keep a settled run's outcome (FG11d)

* test(managed-agent): reuse a same-key cancel (FG11d)

* test(managed-agent): refuse a second fire while the turn runs (FG11d)

* revert: move the second-fire cell onto its own branch

The cell belongs to the next PR in the chain, which bases on this branch, so
it cannot also live here: the next PR's diff would be empty. Content of this
branch is unchanged from acdab642b7.

* test(managed-agent): refuse a second fire while the turn runs (FG11d)

* docs(managed-agent): name the settled run's real refusal code

The cell's own comment still said task_already_settled while the assertions
and both design docs record 409 task_action_unavailable, which is the code the
measured behaviour actually answers.

* test(managed-agent): assert the replayed cancel operation's identity

The cell discarded both cancel responses, so "reuses its operation" rested on
the run state alone — which a second operation could also have produced. It now
compares the two operation ids, pins task_id to the cancelled task, and asserts
the replay flag flips false then true.

* docs(managed-agent): separate the queued-input claim from the settled one

The bullet ran the two claims together with no separator, so a reader could not
tell whether "a queued input cancelled before any attempt" was delivered too.
Both language versions now take the same form as the neighbouring bullets, and
the Chinese continuation line gets its indentation back.

* docs(managed-agent): leave the queued-input arm open and fix its carriers

Review round 2. R1-1: the queued-input criterion is reachable, so the bullet
no longer books it delivered — under queue_one (and allow below quota) a second
manual fire is admitted while the first run is active, because hasOpenTurn reads
only public ma_turn rows. The cell pins the default-skip refusal, not that arm.
R1-10: the cell's comment now names the count arm instead of the open-Turn arm.
R2-1/R2-2: the workflow's two budget comments and both derivation ledgers record
this cell's 25 s (fork 11212, healthy 10612).

---------

Co-authored-by: 易良 <[email protected]>
2026-10-22 03:16:14 +00:00
ytahdnand钉萁 257b9f5413 fix(core): avoid false recovery after directory changes (#15012)
Co-authored-by: 钉萁 <[email protected]>
2026-10-22 10:38:24 +08:00
9a913c7302 docs(skills): document .agents/skills and skills.directories (#15086)
* docs(skills): document .agents/skills and skills.directories

* docs(skills): note the trust caveat where skills.directories is recommended

* docs(skills): spell out the trust scope of skills.directories and widen the path checks

---------

Co-authored-by: A.Shpak <[email protected]>
Co-authored-by: 易良 <[email protected]>
2026-10-22 10:35:29 +08:00
zjgzx1988 4515275b17 feat(shell): remind the model to record files a command writes (#15055)
* feat(shell): remind the model to record files a command writes

Shell commands that create or rewrite a CSV never pass through the tools
that auto-record artifacts. Ask for record_artifact in the successful
result the model reads, without showing that reminder in the terminal.

* fix(shell): remind about artifacts only when recording can happen

Skip the reminder when session artifacts are disabled and when the command
is read-only, and state the workspacePath and title requirements the tool
actually enforces.

* fix(shell): skip the artifact reminder when the config has no flag

Partial configs used by commit-tracking tests do not implement
isRecordArtifactEnabled. Treat a missing method as recording disabled
instead of throwing on every successful command.
2026-10-22 10:19:51 +08:00
Bob.qwencodeandgithub-actions[bot] 8f259db527 chore(release): v0.26.0 [skip ci]
* chore(release): v0.26.0

* docs(changelog): sync for v0.26.0

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-22 02:08:04 +00:00
Shaojin Wen 8a9ae631a3 test(cli): canonicalize the review git fixture so R32-2 holds under a symlinked tmpdir (#15133)
`tmpdir()` is a symlink on macOS (`/var` -> `/private/var`). R32-2 runs
`releaseWorktree(join(repo, 'wt'))` from the launch worktree, so the
ancestor-symlink walk never meets the fixture root and stops at the
tmpdir symlink: the release is refused before `worktree remove`, the
shim's rename never happens, and the test's own `chdir` into
`.qwen/tmp/renamed` throws ENOENT. Every macOS run of the file fails
there.

Resolve the fixture root once in `beforeEach`. The same spelling also
makes the `not.toContain(join(repo, ...))` checks meaningful on macOS,
where git prints canonical paths and the raw spelling never matched.
2026-10-22 08:48:27 +08:00
ChiGao 0ac1fcca91 feat(cli): align OpenTUI input, editing and status lines with Ink (#14964)
* fix(cli): restore bounded OpenTUI dialogs and add migration gates

* fix(cli): preserve OpenTUI input and attachment ownership

* feat(cli): align OpenTUI status lines with Ink

* fix(cli): align OpenTUI editor and cancellation transactions

* fix(tui): harden migration cleanup and evidence after review

* test(tui): fail acceptance on unhandled errors

* docs(cli): clarify consolidated OpenTUI delivery status

* test(cli): isolate system settings in TUI parity fixtures

* fix(cli): settle OpenTUI cancellation when replay resets a session

* style(cli): format merged OpenTUI test callbacks

* test(cli): preserve visible input across session rotation

* test(cli): await cancellation before releasing held response

* fix(cli): preserve completion and escape precedence and validate status colors

* fix(cli): preserve attachment navigation with one completion

* fix(cli): display session titles in the OpenTUI composer border

* fix(cli): discard generated command work after cancellation
2026-10-22 08:22:04 +08:00
jinyeandQwen-Coder d03b3116da feat(managed-agent): Add durable manual context compression (#15104)
* feat(managed-agent): Add durable manual context compression

Add idle-only public and WebShell compression operations with fenced native
maintenance, atomic journal results and compressed model-history recovery.
Preserve the raw transcript, pending input and tool results across retries.

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

* test(managed-agent): Align assertions and pin compression Hook recovery

Match the permission tooltip and the existing Managed Workspace label while
preserving authorization and registration assertions. Verify that an unknown
Hook outcome retains the durable generating phase and recovery barrier.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-22 08:09:13 +08:00
烽宁and易良 79242671bb test(ci): guard no-AK membership and filter resolution (#14989)
* test(ci): guard no-AK membership and filter resolution

* test(ci): require non-blank no-AK exemption reasons

---------

Co-authored-by: 易良 <[email protected]>
2026-10-22 05:06:15 +08:00
jinyeandQwen-Coder 13a4fea516 test(managed-agent): align Hosted fault gates with shared ownership (#15066)
Co-authored-by: Qwen-Coder <[email protected]>
2026-10-21 16:45:59 +00:00
Shaojin Wen fdb75a828d test(managed-agent): share the quote and camel gate helpers (FG13) (#15047)
* test(managed-agent): share the quote and camel gate helpers (FG13)

* test(managed-agent): finish the quote and camel extraction (FG13)

* test(managed-agent): pin the shared helpers in the default lane (FG13)
2026-10-22 00:29:11 +08:00
Shaojin Wen fbeb188b16 test(managed-agent): reuse a same-key cancel (FG11d) (#14997)
* test(managed-agent): keep a settled run's outcome (FG11d)

* test(managed-agent): reuse a same-key cancel (FG11d)

* test(managed-agent): refuse a second fire while the turn runs (FG11d)

* revert: move the second-fire cell onto its own branch

The cell belongs to the next PR in the chain, which bases on this branch, so
it cannot also live here: the next PR's diff would be empty. Content of this
branch is unchanged from acdab642b7.

* docs(managed-agent): name the settled run's real refusal code

The cell's own comment still said task_already_settled while the assertions
and both design docs record 409 task_action_unavailable, which is the code the
measured behaviour actually answers.

* test(managed-agent): assert the replayed cancel operation's identity

The cell discarded both cancel responses, so "reuses its operation" rested on
the run state alone — which a second operation could also have produced. It now
compares the two operation ids, pins task_id to the cancelled task, and asserts
the replay flag flips false then true.

* docs(managed-agent): separate the queued-input claim from the settled one

The bullet ran the two claims together with no separator, so a reader could not
tell whether "a queued input cancelled before any attempt" was delivered too.
Both language versions now take the same form as the neighbouring bullets, and
the Chinese continuation line gets its indentation back.
2026-10-21 16:15:54 +00:00
易良 21337f0e51 fix(release): cover every spawned vendored binary in the tarball exec-bit gate (#15008)
* fix(release): cover every spawned vendored binary in the tarball exec-bit gate

The gate matched only vendor/ripgrep/*/rg, but the tarball spawns a second
vendored executable: vendor/landlock-run/<arch>-linux/qwen-landlock-run. Its
own runtime heal shares the ripgrep heal's hard limit — it can only chmod an
install the running user owns, so a root-owned sudo install degrades the
same silent way. Widen the expected set with a small spec list keyed on the
leaf binary's presence (the landlock-run/src source directory falls out
structurally), parse every regular-file tar entry instead of a single
ripgrep regex, and extend the fixture tests to cover both trees with
pass/reject cases.

Also correct the stale comment above the landlock heal: it claimed npm
archives strip non-bin exec bits, which is the inverse of what #12668
established — pnpm's packer wrote 0644 for every non-bin entry from 0.24.4
to 0.25.3; npm keeps working-tree modes.

Refs #12668

* fix(release): keep missing-binary strictness in the widened exec-bit gate

Deriving the expected set from the leaf binary's presence meant a platform
directory whose binary was dropped during staging silently shrank the
expected set: a dist without vendor/ripgrep/x64-linux/rg but with the
directory would pass with five entries. Platform membership now comes from
the directory name alone — ripgrep skips '-win32', landlock-run skips the
'src' source directory — so a missing binary fails as 'missing from the
packed tarball' again, matching the pre-widening strictness. Also update the
function doc block, which still claimed the gate covered ripgrep only.

Regression test: an empty ripgrep platform directory fails with the
missing-entry diagnostic even when every other binary passes.
2026-10-21 15:39:07 +00:00
Shaojin WenandQwen-Coder e63a0bfc23 feat(serve): add daemon token persistence (#15023)
* feat(serve): add daemon token persistence

Add an opt-in 0600 token sidecar for daemon start --save-token, a daemon token command to read or remove it, and stop cleanup. The discovery record and daemon log never carry the token.

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

* fix(serve): scope daemon token sidecar to its instance

Key the persisted token by port and pid so a new daemon on a reused port cannot read the previous instance's token. Only remove the sidecar after the discovery record ownership check succeeds.

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

* docs(serve): key daemon token sidecar by pid

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

* fix(serve): expire daemon token sidecars with their record

Clear the pid-scoped token sidecar on status prune, daemon exit, and a new start on the same port, so a plaintext credential cannot outlive its daemon. Add sidecar permission, read/remove, and stale-scan tests.

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

* fix(serve): preserve persisted token across restart

Capture the old pid's sidecar before stopping and rewrite it for the pid that actually started on the recorded port. Add a restart regression test.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-21 23:06:37 +08:00
易良 f036ab2360 fix(ci): name the sdk-java host lock holder when a ticket starves (#15031)
Three main runs went red today (2026-10-21) with 'host lock not acquired
within 90 minutes as queue ticket N; head of queue is N-...': the starved
waiter WAS the head of the ticket queue, so the flock is held by an
unticketed process -- a stray left by a canceled job, the bare-flock
fallback of a resumed release tag, or another machine sharing the runner
HOME. The current log cannot name any of them, and each occurrence costs
a 90-minute lane plus a flaky-red main.

Add best-effort holder diagnostics around the existing queue, with no
change to ticket, timeout, or error-signature semantics: the first time
the head fails a lock probe it prints a one-shot ::warning:: with the
holders visible via lslocks (fuser fallback), and the timeout error now
lists the holder under the unchanged ::error:: line. An empty answer
means the holder lives on another machine sharing this HOME, which is
itself the topological evidence needed for the follow-up fix.
2026-10-21 22:42:13 +08:00
易良 9c94ae1687 test(managed-agent): wait for the D7-2 runtime park, not a takeover refusal (#15046) 2026-10-21 22:40:49 +08:00
Shaojin Wen f2fabaaa83 test(web-shell): pin the openable session PR filter directly (#15059)
Follow-up to #14994. The helper that picks the bound PRs the badge can
open is exported so the sidebar can tell whether a badge renders, but
its contract was only covered through a sidebar layout test, whose
failure points at layout rather than at the filter. Pin it directly:
bindings that can't be opened are dropped and the rest keep binding
order.
2026-10-21 22:40:14 +08:00
易良andQwen-Coder a4562edb67 fix(ci): align Java integration database and lock lifetime (#14936)
* fix(ci): align Java integration database and lock lifetime

* test(ci): re-pin the sdk-java lock and MySQL probe after the single-step merge

The merge commit staged the conflict resolution before these two re-pins were
written, so they land as a follow-up in the same push:

- `keeps the host lock out of every Maven child` counted three lock-bearing
  steps; #14903 collapsed the MariaDB lane's two Maven steps into one, so the
  count is now two (test job + mysql-integration).
- The MySQL startup probe took the step's first `mvn` line, which is now the
  `-DskipTests install` staging command; select the `-Pmysql-integration`
  command instead so the probe still exercises the consumer.

Verified: vitest sdk-java-workflow.test.js 96 passed; prettier --check clean.

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

* fix(ci): skip the MySQL binlog, drop the unused database, correct the reaper bound

Three independent corrections to the mysql-integration lane this PR reworks.

1. MySQL 8 enables the binary log by default (MariaDB 10.11 did not in the
   configuration this lane replaced), and the datadir is an uncapped tmpfs.
   Nothing in packages/managed-agent-server/src or packages/sdk-java reads a
   binlog (0 hits for binlog|log_bin|GTID|SHOW MASTER STATUS), and the
   container is disposable with no replica, so `--skip-log-bin` is appended
   after the image name beside --innodb-use-native-aio=0. Without it the
   ~67 MiB hex-encoded LONGBLOB query this lane already raises
   max_allowed_packet for is serialized a second time into host RAM.

2. `--env MYSQL_DATABASE='runtime_broker_test'` had no consumer: the lane's
   only connection is -Dmysql.url=.../managed_agent_test?createDatabaseIfNotExist=true.
   The declaration invited a cleanup that drops createDatabaseIfNotExist and
   dies at first connect with Unknown database. Dropping it lets the JDBC URL
   own the schema and shrinks the diff.

3. The cleanup comment and removal-failure warning said the host reaper
   reclaims a stranded container "after a 24h age gate" / "up to a day".
   The real bound is ~48h: qwen-docker-cleanup.sh:31 gates on a strict
   `now - created_at > 86400` (exactly 24h is SKIPPED) and
   qwen-docker-cleanup.timer fires once a day, so a container created just
   after a fire is skipped at the next one and reaped at the one after.

Test pins updated in the same change: the start-step assertion, the exact
docker-run trace literal, and the cleanup warning literal. All three were
mutant-verified red (drop --skip-log-bin / re-add MYSQL_DATABASE / restore
"after 24h") and restored byte-identical.

Only this PR's own copy of the reaper wording is corrected; the two
pre-existing copies (sdk-java.yml's event-transport-multi-node job and
release-managed-agent-reference.yml) are untouched to keep this diff scoped.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-21 14:39:26 +00:00
jinyeandQwen-Coder bca0cf80b9 feat(web-shell): unify daemon and managed workspace navigation (#15061)
* feat(web-shell): unify daemon and managed workspace navigation

Extract shared workspace navigation, task target selection and execution choice
from the stacked integration so hosts can use them with the existing Managed
provider on main.

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

* fix(web-shell): keep workspace directories stable during refresh

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-21 14:23:48 +00:00
易良andQwen-Coder e8ddd0d79a fix(core): pluralize the Goal token budget stop reason (#15062)
goalTokenBudgetReason interpolated "tokens" unconditionally, so a Goal
armed with a budget of exactly one token stopped with "(1 tokens)" in
its lastReason -- the string a user reads when deciding whether to
resume. The turn and active-time reason builders in the same file
already branch on the count; follow that shape.

Closes #10588

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-21 22:06:57 +08:00
39228ac51e feat(cli): paste clipboard text with Ctrl+V on Linux (#14172)
* feat(cli): let Ctrl+V paste clipboard text on Linux

On Linux the only clipboard reader in the TUI was the image path, so pressing
Ctrl+V with text or a link on the clipboard either reported a missing
wl-clipboard/xclip or did nothing at all, and users had to reach for the
terminal's own paste. Reported from our Ubuntu user group, where the "install
wl-clipboard" hint sent people off in the wrong direction entirely.

Reads are driven by what the clipboard itself lists: `wl-paste --list-types`
(Wayland) and the `TARGETS` atom (X11) are enumerated and classified against
the names the standards define - ICCCM's text atoms plus `text/plain` are the
preferred read, any other `text/*` is the fallback, and ICCCM's bookkeeping
properties are never requested. Each read asks for the exact name the tool
reported, so `text/plain;charset=utf-8` is requested under that name rather
than a guessed one, and a type the clipboard already offers needs no code.

A paste too large for the buffer still collapses to a placeholder, and because
the user never reads content that stays folded, its expanded form is described
to the model: attributed to the clipboard, given the standing of a file the
user asked to open rather than a message from them, and closed from outside so
a forged end marker inside the data cannot promote what follows it. Both
collapse routes are covered. Shell mode is not, because there the expanded
placeholder is the command that gets recorded and run.

Anything the clipboard cannot answer - no display server, tool missing, spawn
failure, timeout, empty or whitespace-only selection - resolves to "nothing to
paste" rather than an error. macOS and Windows behavior is unchanged.

* fix(cli): harden Linux clipboard text paste against review findings (#14172)

- Decode clipboard stdout as one byte stream (Buffer.concat once) so a
  multi-byte UTF-8 character split across pipe chunks is no longer
  replaced by U+FFFD, and bound one read at 16 MiB so an oversized
  selection is discarded instead of crashing the process on the
  platform string-length limit.
- Escape literal clipboard-paste markers inside collapsed payloads so a
  pasted </clipboard-paste> cannot close the framing block early and
  promote injected text to user-level prose.
- Store collapsed pastes raw and apply describeClipboardPaste at submit
  time, keyed on the submit-time shell-mode read: exiting shell mode
  with Escape can no longer strip the attribution, and Ctrl+S stashing
  round-trips the pasted characters instead of the model-facing prose.
- Fall through to the text read when an image is detected but its save
  fails (e.g. an image/bmp offer with no converter), keeping the
  validated key.pasteImage route off the clipboard re-read.
- Add an in-flight latch and a submit epoch to handleClipboardPaste so
  a second Ctrl+V cannot double-insert and a read resolving after
  submit is dropped instead of landing in the cleared buffer.
- Classify an all-image-paths clipboard before the size test so large
  path lists become attachments like the bracketed route, share one
  collapseLargePaste helper between both paste routes, unify X11 image
  detection on CLIPBOARD_IMAGE_TYPES, and drop the trailing line
  terminator selections habitually carry.

* fix(cli): frame pasted blocks once and guard late clipboard reads (#14172)

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

* fix(cli): close clipboard-paste review findings on framing, staleness, and file URLs

- reject file:// URIs with a remote host in pasted-image classification so
  a UNC conversion can never reach fs.stat (SMB credential exposure)
- append the attribution prose only when a placeholder actually matched,
  and release discarded paste state on ESC-ESC/'!' clears
- make the marker escape injective by escaping the escape character first
- guard image-path promotion continuations with the submit epoch
- bump the submit epoch on history recall, queue pop, search
  cancel/accept, and Ctrl+C buffer replacement
- stop trading a successful whitespace-only clipboard read for a
  lower-tier text/html copy; keep looking when a uri-list converts empty
- delete the zero-caller describeClipboardPaste export and avoid the
  double expansion on framed submits
- derive the fileURLToPath expectations so the suites pass on Windows

* fix(cli): close clipboard-paste review findings on fidelity and staleness (#14172)

- rank the X11 plain tier by encoding fidelity and retry legacy
  STRING/TEXT reads as Latin-1 when UTF-8 decoding fails (R5-4)
- treat a terminator-only plain-text read as nothing to paste instead
  of falling through to the selection's text/html markup (R4-13)
- classify the slash-command framing exemption on the trimmed,
  attachment-free value the dispatcher will route on (R4-4, R6-1)
- scope the paste attribution prose to the delimited block and declare
  the payload's doubled backslashes (R8-1, R7-1)
- supersede in-flight clipboard reads at the shell-history recall,
  mouse suggestion-accept, and export-cycle buffer replacements
  (R4-9, R7-4)
- keep pasted image paths as shell-mode command arguments instead of
  attachment chips (R7-2)
- fall back to clipboard text on the terminal paste route when the
  image save fails (R7-3)

* fix(cli): keep clipboard guards closed for promotion and completion accepts

- Hold clipboardPasteInFlightRef across the image-path promotion by
  awaiting insertClipboardText, so a second Ctrl+V cannot start a
  duplicate read/copy loop.
- Bump submitEpochRef on the keyboard completion accept, matching the
  mouse accept, so a late clipboard read cannot splice into the
  replaced buffer.
- Decide the Latin-1 retry on UTF-8 validity rather than on U+FFFD, so a
  well-formed payload that legitimately contains a replacement character
  is not re-decoded as Latin-1.

* fix(cli): route vim-mode submit through the composer submit path

Vim's Enter cleared the buffer and called handleFinalSubmit directly, so
it skipped large-paste placeholder expansion, the submit epoch bump and
the pending-paste cleanup. The composer now registers its submit path and
the vim hook calls it, falling back to handleFinalSubmit while no
composer is mounted.

* fix(cli): supersede in-flight clipboard reads on Ctrl+U

Ctrl+U clears the line through BaseTextInput but did not bump the submit
epoch, so a clipboard read started before the clear could insert into the
emptied buffer. Bump the epoch in the same branch that resets the export
cycle, and pin it with a drop test.

* test(cli): narrow the registered vim submit handler before calling it

The UI action is optional, so calling it directly failed tsc --build with
TS2722 in the lanes that compile the test tree.

* fix(cli): keep the paste framing out of the human-origin input

Main's human-origin input (#14876) reads what the user submitted, with
pastes expanded raw. Build it from the raw expansion rather than the
model-facing value, so the framing prose and markers stay on the model
side only. Composer submit expectations now carry the display text,
human input and paste expansions main added to the submit options.

* fix(cli): parse pasted file: URIs with one case-insensitive authority

Both file-reference gates matched a lowercase `file://` prefix, so a
legal `File://host/…` spelling skipped the remote-host drop and was
promoted as an image path, and `file:/a/shot.png` was never converted.
Classification and the uri-list conversion now share one URL-based
helper: any scheme case and every RFC 8089 spelling converts, a remote
host or an unconvertible URI is not an image path, and UNC-shaped
tokens, which fs.stat would authenticate to on Windows, are dropped.

* fix(cli): supersede in-flight clipboard reads on every line-kill and buffer replacement

Only Ctrl+U advanced the submit epoch among the keys BaseTextInput
answers by killing a line or replacing the buffer, so a read that
resolved after Ctrl+K, the external editor or undo/redo spliced its
payload into the result. Match all of them through keyMatchers (the
bindings are configurable) plus the buffer's own undo/redo keys, in
one place before the fall-through.

* test(cli): pin a vim composer submit's display text apart from its model text

Vim drops the submittedPrompt projection, so the transcript, session
log and cancel-restore depend on the display text the composer submit
carries. Pin that it reaches the queue as the placeholder form while
the model reads the framed paste.

---------

Co-authored-by: Shaojin Wen <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
2026-10-21 21:44:33 +08:00
qwen-code-dev-botand易良 b69a0f5814 test(cli): pin daemon workflow intent e2e off the container sandbox (#15022) (#15029)
The docker E2E leg exports QWEN_SANDBOX=docker for the whole run. The
daemon inherits it and spawns `qwen --acp` children, each of which hops
into its own sandbox container; from inside, the fake OpenAI server on
the host's 127.0.0.1 is unreachable, so every prompt turn fails and all
five tests in this file go red.

Every other daemon test that drives a host-loopback fake model skips
under a container sandbox or pins QWEN_SANDBOX=false for its subprocess
tree. Do the latter here so the suite keeps running in the docker leg.

Co-authored-by: 易良 <[email protected]>
2026-10-21 21:37:34 +08:00
qqqys 7bda51a070 feat(core): add model.loopProtection and remind instead of stopping loops by default (#15016)
* feat(core): add model.loopProtection and remind before loop stops

Add the model.loopProtection setting (stop, warn, off). Under the default
stop, every stop point stays where it is; the always-on guards (identical
calls, shell inspection, daemon invalid tool parameters, stuck repetition
past the adaptive cap) first send the model one reminder with the next tool
results. warn only reminds, except for content and thought repetition; off
neither reminds nor stops. Budgets stop in every mode.

A new loop_protection telemetry event records each reminded, stopped and
observed decision. Loop stop hints and the TUI dialog note name the setting.

Refs #14828

* feat(core): default model.loopProtection to warn

A firing loop guard now reminds the model once and lets the turn continue
by default. Budgets still stop every mode, and content and thought
repetition still stops. Set model.loopProtection to "stop" to keep the
previous stop points.

Refs #14828

* fix(core): keep side queries out of the running turn's loop decisions

A concurrent /btw side query shares the client's loop detector. Its retry
dropped the turn's pending reminders, its stateful tool results could queue a
global-duplicate reminder into the turn, and under `off` it marked patterns
observed under the turn's prompt id. Route recordToolResult through the same
ownership gate, drop pending reminders once on the owner's Retry only, and
skip observed decisions for side queries.

Warn once when model.loopProtection is not a valid mode instead of silently
using the default, and update the consecutive-identical comment that still
described an unconditional halt.

* fix(core): keep reminders across side queries and guard unknown modes

A concurrent /btw side query starts as a UserQuery, so its reset cleared the
running turn's queued reminders and reminded/observed decisions. Under `warn`
the stuck turn then got no reminder at all. Keep that state on a side query's
reset, and log each reminder under the prompt id that queued it.

Validate model.loopProtection in core Config as well, since SDK and serve
embedders bypass the CLI check and an unknown mode acted as `off`.

For a stateful read tool with no recorded results, `warn` now records an
observed decision at the stop point instead of a reminder claiming the
result will not change; `stop` still fails safe and stops.

* fix(core): let late stateful results earn the warn reminder

Under `warn`, a stateful read that reached the stop point before its results
were recorded was observed under the 'identical' key, which then blocked the
reminder for the rest of the streak even after the results came back
unchanged. Record that case under its own key so the reminder can still be
sent once the evidence is in.

In daemon sessions, leave queued loop reminders untaken when the repeated
tool failure guard stops the batch, so they are not logged as `reminded`
without being sent.

Update the telemetry docs for `observed` under `warn`, note in
model.skipLoopDetection that model.loopProtection decides whether the
always-on guards remind or stop, and drop the stale DashScope #5019 rationale.
2026-10-21 13:11:16 +00:00
易良 699104d448 feat(serve): make paired Web Shell devices listable and revocable (#15000)
Pairing minted a credential with no way to name or withdraw it: the store
held an unindexed set of digests, the exchange response carried only the
token, and the sole reset was a daemon restart.

Key each device credential by an id the exchange now returns, list paired
devices without credential material, and revoke one device behind the same
bearer authorization as pairing issuance. This is the daemon half of
"deleting a connection deletes its token" in #14947; the desktop catalog and
the client-side wiring are separate decisions tracked there.
2026-10-21 20:53:38 +08:00