Commit Graph
2941 Commits
Author SHA1 Message Date
Shaojin Wen 14de8a035e feat(verify-pr): inherit --publish consent on same-session re-verification (#16112)
* feat(verify-pr): inherit --publish consent on same-session re-verification

A maintainer who ran `/verify-pr <n> --publish` and then typed the
"有更新了,重新验证" follow-up trigger had to confirm publishing again:
the skill inherited the previous round's coordinates but deliberately
excluded publish consent, so every re-verification of an already
published PR repeated a confirmation the maintainer had just given.

Let a follow-up round inherit publish consent from the most recent
round for the same PR in the same session. The round must announce the
inheritance in one line before posting, `--no-publish` on the trigger
opts out and breaks the chain, and the most-recent-round semantics keep
a quiet round from reviving an older consent. Cross-session rounds
still require an explicit request: the phrase alone posts nothing, and
a published comment found on the PR is evidence of a past round, not
consent for a new one. The CI lane and watch mode are unchanged.

Update the two pin tests that fixed the old per-invocation-only rule
and pin the inheritance, announcement, opt-out, and cross-session
boundaries.

* fix(verify-pr): carry inherited publish consent through follow-up chains

Review of the first round found the consent chain broke after a single
inheritance: R1 invoked with --publish, R2 a bare trigger that inherited
and published, R3 a bare trigger whose most recent round (R2) was not
itself invoked with --publish — so R3 fell back to asking again, the
exact friction this change exists to remove.

Model consent as a per-PR state of the session instead of keying it to
the latest invocation: a --publish invocation grants it, a fresh
/verify-pr round without --publish or a trigger carrying --no-publish
withdraws it, and a bare trigger keeps it unchanged, so consecutive
bare triggers publish under the original grant until a round withdraws
it. Watch rounds keep their own standing consent and never change this
state. Also close the two review suggestions: the Hard rules bullet now
recognizes a request inherited within the same session, and the
argument hint lists --no-publish so the opt-out is discoverable.

Pins cover the grant/withdraw/keep mechanics, the three-round chain,
the watch boundary, the Hard rules clause, and the updated hint.

* fix(verify-pr): grant session consent on any explicit publish request

Second review round found two gaps. The design docs for the assets
coordinates still stated that publishing consent is per invocation,
which reads as an invariant and could silently revert the feature;
both language versions now defer to the skill's session-consent rule
while keeping "coordinates are not consent". And the grant keyed on
the literal --publish flag, while the publish rule also accepts an
explicit request in words — a words-request round published but seeded
no consent, splitting the friction this PR removes by request form.
The grant now rides on the explicit request, flag or words alike, and
the withdrawal matches it.

Also close the deep verification's coverage survivors: the withdrawal
is pinned as one contiguous sentence (a fresh round keeping consent
now fails), the chain's upper bound, the announcement's naming duty,
and the evidence-of-a-past-round clause each have pins, and an
inherited publish now carries its one-line announcement into the
comment body immediately before the attribution footer, so a PR
reader can tell it from a per-round request.
2026-11-02 22:22:00 +08:00
qqqys 73474ef094 fix(mods): skip hooks on prompt.paint and ui.place instead of failing (#16124)
A Mod that hooked either event failed to initialize, which stopped a headless run before any model request. Both are now skipped with the startup line, like other events Qwen Code does not run yet. $.session.version() reports 2.1.316.
2026-11-02 12:50:30 +00:00
jinyeandQwen-Coder d9d6fc4a4b refactor(managed-agent): split Agent and Session stores (#16090)
* test(managed-agent): audit lock order across store collaborators

Record the bilingual Store decomposition contract and entry ownership,
transaction, lock and caller inventories. Follow final collaborator fields
across source files while preserving owner-bound facade populations and
existing ambiguity, transaction and compiled-member checks.

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

* refactor(managed-agent): split Agent and Session stores

Move Agent and Journal responsibilities into immutable internal collaborators while retaining transaction facades and compatibility bridges. Resolve Journal dependencies at construction and migrate factories, fixtures, lock auditing and the bilingual implementation contract.

Record focused verification and outstanding full-gate requirements separately.

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

* fix(managed-agent): repair store decomposition verification

Keep declared field receivers visible across method-local type scopes while retaining fail-closed nested class handling. Preserve the Hosted CSI dependency identity assertion at its new owner.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-02 12:46:43 +00:00
BaboBen 84c4c70789 fix(channels): allow request owners to answer one-time tool permissions (#16087)
* fix(channels): allow request owners to answer one-time tool permissions

* docs(channels): record permission-owner verification
2026-11-02 19:41:43 +08:00
Shaojin WenandQwen-Coder 60f5c1ed95 docs(managed-agent): record the completed store constructor injection in the ledger (#16118)
#16061 replaced the seven ManagedAgentStore public setters with a single
@Autowired constructor holding final references, but the parent
structure-review ledger still described the setters as current state and
framed the conversion as future work. Update the D1 row note, the prose and
the P1 store narrative to the implemented state in both languages, keep the
setter investigation as pre-conversion context, and leave the physical
aggregate split as the remaining work.

Figures re-measured against origin/main (0 public setters, 1 @Autowired
constructor, final fields, 7657 lines); prettier and bilingual heading/link
checks pass. Documentation-only.

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-02 17:38:19 +08:00
qqqysandShaojin Wen 9c96f5aa82 feat(mods): run prompt.submit hooks in headless runs (#15905)
* feat(mods): run prompt.submit hooks in headless runs

Mods' prompt.submit hooks can rewrite, add context to, or drop each headless prompt before UserPromptSubmit, @ processing and the transcript see it. Terminals keep skipping these hooks until they raise the event. Includes the design document for the four steps.

* test(mods): update bundled worker load response expectation

* fix(mods): preserve downstream prompt submit outcomes

* fix(mods): isolate prompt fallback from host continuations

* fix(mods): preserve latest local prompt outcome in recovery

* fix(mods): hear only a message's text and take any answer after next

A stream-json message with an image block reached the prompt.submit
hooks as its serialized blocks, so they were skipped as too long or saw
JSON noise, and a rewrite carried that JSON into humanInput and
turn.start. The hooks now hear its text blocks; a rewrite replaces them
and the other blocks follow it.

A hook's answer after its next changes nothing, but it was still
validated, so a hook that only watches and returns nothing, or spreads
the event back, failed and logged a skip on every prompt. Only a drop
there is now checked.

* fix(mods): keep prompt.submit to typed text and drop empty rewrites

- Show only the rewritten text in the transcript and UserPromptSubmit,
  not the serialized non-text blocks of a stream-json message.
- Skip prompt.submit for SDK messages that answer a tool call or carry
  tool blocks, and for messages without text.
- Drop a prompt the hooks rewrote to nothing instead of sending the
  model an empty prompt.

* fix(mods): send each prompt context once on its own

The Mods' prompt.submit context and UserPromptSubmit's additionalContext
share one hidden block, and the block was only skipped when the
conversation already held it whole. A Mod whose context changes every
turn made every block new, so an unchanged UserPromptSubmit context went
to the model again each turn.

The injected part now records the contexts it joins, and a context counts
as delivered when an earlier block holds it, so only the contexts the
conversation lacks are sent. A block without that record, as after
resume, still counts as one context.

* fix(mods): keep a message's blocks in the transcript after a rewrite

A stream-json message with blocks other than text is recorded with those
blocks serialized after its text. A prompt.submit rewrite recorded the
rewritten text alone, so the same kind of message showed two shapes in the
transcript. The rewritten prompt is now recorded with the blocks after it.

Adds the headless E2E test plan and its results.

---------

Co-authored-by: Shaojin Wen <[email protected]>
2026-11-02 09:14:14 +00:00
qqqys 1e250b391b feat(acp): run re-entered, channel and Goal-proposal turns on the shared loop (#15902)
* feat(acp): run re-entered, channel and Goal-proposal turns on the shared loop

With experimental.unifiedAgentLoop on, channel prompts and Goal-proposal
approval turns run as a UserQuery on the shared round, the shared tool
batch and the client's stop point, and a retry, a continue and the
restore of an unanswered ask_user_question run there with a Retry first
round. ACP still strips and restores the re-entered attempt, still runs
UserPromptSubmit on a retry through admitUserPrompt, and still answers a
restored question with its own tool step.

Core: userTurnRecordedByHost also covers a Retry's todo work chain,
promptAdmission is accepted on a Retry, and the new input
historyPromptId gives a Retry's request the identity of the attempt it
replaces.

The ACP parity suite gains a channel prompt and a retry after a model
error.

* fix(acp): treat a re-entered turn's pushed first request as sent on every exit

On the shared round, whether the first request reached history is now
read when the round ends, on every exit. A retry whose request was
pushed and whose response then failed no longer puts the stripped
attempt back beside it, and a restore's one-shot notices are taken once
its answer is in history.

A continue whose first request never goes out, cancelled at the loop
top or failed, puts the interrupted run back as it was and keeps the
worktree notice its dropped message took pending. A restore cancelled
before its request went out keeps the full answer and takes its
notices.

The stripped-run restore and the Goal-proposal settlement block are
shared helpers. The parity suite runs the channel scenario from a
trusted parent so the channel mark survives, and adds a retry that
fails again. Docs and comments are corrected.

* fix(acp): keep a Goal proposal unsettled when its turn gives way or ends early

In plan mode, the Todo Stop Guard on the shared round now still yields
to a queued prompt or to input the round took, so a Goal proposal is not
started from a turn that gave way.

The client no longer drops a host-owned Goal proposal when a send ends
abnormally. The host takes it when its turn ends, so a cancelled
proposal turn still reports that the Goal did not start.

A Retry whose admitted prompt is blocked puts back the attempt the
client stripped from history.
2026-11-02 17:14:05 +08:00
qqqys 9d2ee877c2 feat(web-shell): plot per-request speed and failures in token usage (#16111)
* feat(web-shell): show request success rate and distinct model colors in token usage

The token usage panel tracked failed API calls but never showed them, and
models were hard to tell apart: the first two series colors were both blue,
every share bar started from the same blue, and the card had no color mark.

- Add a success rate to the overview and to each model card, with a status
  icon (ok / warning below 100% / error below 90%) and the failed count.
  Each retry counts as its own call, as in `api.totalErrors`.
- Add session output speed to the overview, weighted over all timed models.
- Replace the model palette with six categorical colors checked for
  color-vision-deficiency separation, with dark-theme steps. Colors follow
  the order models were first used, so a model keeps its color when the
  token ranking changes; models past the sixth share a neutral color.
- Add a stacked per-model share bar with a legend, and a color strip on
  each model card. Share text keeps the foreground color.

* feat(web-shell): plot per-request speed and failures in token usage

Token usage showed one average output speed per model, so a slowdown
mid-session or a few slow outliers looked the same as a steady rate,
and failed calls had no time or kind.

- Core keeps, per model in the stats buckets, the latest 200 timed
  responses (time, first token, generation window, output tokens,
  subagent) and failed calls by status code or error type with the
  latest 50. Session replay rebuilds both from stored events.
- The daemon stats route returns them as optional `timingSamples` and
  `failures`; older daemons omit them.
- Web Shell adds a speed-over-time chart: one dot per request in the
  model's color, a five-point median line, a toggle to first-token
  latency, failure marks, a nearest-request tooltip on hover or tap,
  and a legend that hides series.
- Model cards add an output-speed spread (p50, p10-p90 on a shared
  scale) and failures by kind.
- Add the design doc in English and Chinese, and point the token usage
  hierarchy doc at it for the model palette.
2026-11-02 08:26:53 +00:00
qqqys 6b6df2a8b4 feat(web-shell): show a dismissible Workflow chip and menu summary (#16107)
* feat(web-shell): show a dismissible Workflow chip and menu summary

Workflow moved into the composer + menu, so once it was on the toolbar no
longer showed it. Show a Workflow chip in the toolbar while the saved
preference is on, the same way Plan does: hovering reveals a close mark,
clicking turns the mode off (also during a running response), and an
on-but-unavailable preference is tinted with the warning color. The menu
entry gains a one-line summary of what the mode does, or of its state when
it is not simply available.

* fix(web-shell): keep the Workflow chip warning off the Plan chip selectors
2026-11-02 07:32:44 +00:00
易良andQwen-Coder 73a7268447 test(cli): guard settings reference against schema drift (#16085)
Co-authored-by: Qwen-Coder <[email protected]>
2026-11-02 15:29:30 +08:00
ytahdnand钉萁 8de81eb209 fix(web-shell): tighten mobile composer layout (#16103)
Co-authored-by: 钉萁 <[email protected]>
2026-11-02 14:59:09 +08:00
Shaojin WenandQwen-Coder 64651cde11 docs(managed-agent): reconcile the structure-review ledger with the broker extraction (#16109)
* docs(managed-agent): reconcile the structure-review ledger with the broker extraction

The binding registry and execution workflow slices (#16029, #16084)
extracted the reconciliation/recovery/cancellation cluster, but the parent
structure-review ledger still described a 6,329-line broker with only the
cooldown and hold inventory out, and still called the cluster entangled and
waiting for a registry. Update the D1 god-class rows, the surrounding prose,
the mirrored test size and the P1 extraction narrative to the measured
current state (4,270-line facade, nine collaborators), in both languages.

Every stated figure was re-measured against the source; prettier and the
bilingual heading/link checks pass. Documentation-only.

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

* docs(managed-agent): put the met Registry prerequisite in past tense

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-02 13:41:55 +08:00
Shaojin Wen 0a7f260704 fix(managed-agent): reach the listeners the server resolves in upgrade start and verify (#16051)
* fix(managed-agent): reach the listeners the server resolves in upgrade start and verify

start and verify derived the control-plane URL from server.env's
QWEN_MANAGED_AGENT_SERVER_ADDRESS and SERVER_PORT, and the Harness URL from
its QWEN_MANAGED_AGENT_HARNESS_BASE_URL. A port or address set anywhere else
(config/application.yml, an argument, SPRING_APPLICATION_JSON, a relaxed
SERVER_ADDRESS) or a management.server.port of its own made start wait out
--timeout on a port nothing served.

ResolvedConfiguration now also prints serverUrl, healthUrl and
harnessBaseUrl as Spring binds them: server.port, server.address and
server.ssl, with a wildcard reached on loopback, and the health endpoint by
Spring Boot's management-port rules. start resolves them before it changes
any unit, verify before its rows. --server-url still names the control
plane; a port of 0 or below refuses until it does; a configuration that
does not resolve falls back to the server.env URLs with a notice.

* fix(managed-agent): keep the health name for a blank path mapping

A blank management.endpoints.web.path-mapping.health, such as an unset
${HEALTH_PATH:}, is ignored by Spring Boot's MappingWebEndpointPathMapper,
so the endpoint stays at /actuator/health. The resolver turned it into an
empty segment and sent start and verify to /actuator, where start waited
out its timeout on a healthy server. Fall back to the name when the
mapping is blank, as Spring does.

Also from review:
- the fallback notice says only the Harness URL comes from server.env
  when --server-url names the control plane;
- start's success line names the API URL next to the health endpoint;
- verify with --server-url resolves the configuration only for --tenant,
  as it did before, since it does not reach the Harness.

* fix(managed-agent): refuse listener URLs fetch cannot dial in upgrade start and verify

A link-local server or management address keeps its IPv6 zone in the
resolved URL (http://[fe80:0:0:0:0:0:0:1%1]:8080), and the WHATWG URL
parser behind fetch rejects every zone spelling. The health probe caught
the TypeError as "not UP", so start waited out --timeout on a URL it could
never send. The same held for an out-of-range port, a server.env address
with a zone on the fallback path, and a --server-url that does not parse.

listeners() now checks both URLs with URL.canParse after the no-fixed-port
refusal, before any unit changes, and names the zone when that is why. The
resolver test pins that the zone is kept, so the script can say so. The
verify test lists the health probe with the session reads instead of
slicing it off.
2026-11-02 12:57:52 +08:00
Shaojin Wen d36fdbdfe5 test(managed-agent): gate detach while a tool result is unknown (#16098)
The MCP detach must refuse before it releases anything when an
operation is recovery_blocked/outcome_unknown. Start an ext://hold
read, kill the stdio fixture, and call the Harness detach route; the
case requires the documented refusal, no release control, and the
operation still unknown.

The Hosted fork moves by the case's measured 27.4 s, booked at 55 s.
2026-11-02 11:44:00 +08:00
Shaojin Wen 7b4b353750 test(managed-agent): gate the two-server first release refusal (#16089)
Extend HostedMcpRestartFaultGatesIT with the FG12d cell #15750 still
carried: a Session configured with two MCP servers, the first release
refused before forwarding, and the same-id retry releasing both.

The case adds fixture2 only for its own boot, waits for both
configurations to settle, and requires three release controls: 503,
the same operation id answered 200, then the other configuration's
release answered 200. Both fixture processes must exit.

The Hosted fork moves by the case's measured 16.2 s, booked at 32 s.
2026-11-02 02:46:13 +08:00
jinyeandQwen-Coder 87c1918a6a refactor(managed-agent): Extract runtime execution dispatcher (#16084)
Co-authored-by: Qwen-Coder <[email protected]>
2026-11-02 01:21:15 +08:00
ae7901552e fix(core): settle the loop reminder delivery at the model-acceptance boundary (#15559)
* fix(core): settle the loop reminder delivery at the model-acceptance boundary

takeLoopReminder() cleared the pending map and logged the 'reminded'
telemetry while the ToolResult request was only being assembled. Between
the take and turn.run(), the send still awaits microcompaction and
memory recall and yields pending goal events, so a consumer that stops
the generator there loses the streak's warning (dropPendingReminders
only unmarks still-pending keys) while telemetry credits a delivery the
model never saw.

The delivery is now only prepared at the take: the client commits it
when the first model-accepted event arrives and abandons it — restoring
the pending entries — in the send's finally when the request never
reached the model. A full reset drops a prepared delivery so a late
abandon cannot leak a stale warning into a new prompt. The agent-core
round loop keeps its take-time accounting with an explicit commit since
it has no consumer-visible suspension between the take and the model
call.

Fixes #15537

* fix(core): defer the agent-core loop reminder commit to model acceptance

* fix(core): drop the ineffective pre-round reminder abandons

The three abandonLoopReminderDelivery() calls at the abort/maxTurns/time
checks could not change anything: each is followed by a break out of the
round loop, the detector is method-local, and abandon only moves entries
between two private maps without logging. They made the take-site comment
and the maxTurns control read as if they were what keeps `reminded`
truthful, so drop them and say what the control actually pins — no round
crosses a model-acceptance boundary, so nothing commits.

Also, from the same review round:

- abandon's doc block named the wrong function. A retry drops the streak
  through resetToolCallCount(), which deletes the three hard-coded keys;
  dropPendingReminders() cannot reach entries already taken out of the
  pending map.
- client.test.ts asserted not.toHaveBeenCalled() immediately after
  mockClear(), which cannot fail. Compare the call count with the value
  captured before the send instead, so the "suspended before turn.run"
  premise is actually pinned.
- cover the multi-entry path: commit credits every entry of a delivery and
  abandon restores all of them, not just the first.
- telemetry.md and both languages of the loop-protection design doc now
  say `reminded` is logged at model acceptance, and the "sent, never
  accepted, credited on a later send" case is added to the enumeration of
  not-counted cases.

---------

Co-authored-by: suzhiguo7 <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
2026-11-02 00:43:23 +08:00
Shaojin Wenandverify 4a5c487681 test(managed-agent): gate the expired-cursor resync answer (#16076)
* test(managed-agent): gate the expired-cursor resync answer

FG9e now advances the Session stream's replay floor past a lagging
client and requires one `agent.session.resync_required` frame on both
the public and WebShell streams, with no event id and the floor and
snapshot cursors it must reload through. The same cursor on the plain
events route answers `409 cursor_expired`, and the floor itself stays
readable.

This closes the expired-cursor cell the FG8 tracker (#14163) deferred
and #15750 carried. The case measures 21.3 s locally; the Hosted fork
budget and Verify step move with it.

* test(managed-agent): wait for projection coverage before advancing the floor

Awaitility treats a lambda that returns without failing as satisfied,
so the coverage guard must assert rather than return. Without this a
poll that lands before the lead's Turn is projected would skip the
advance and dereference a null window.

* docs(managed-agent): correct the resync case's tracker references

FG9e now carries the third case, so the relay-hold sentence must name
only the two resume cases. The FG8 record no longer predicts a slice-2
landing and points at the FG9e delivery instead.

* ci(managed-agent): record the resync case's measured range

The 60 s fork-budget entry was written from one 21.3 s run; the final
audit runs measured 21–23 s, so state the range the budget covers.

* docs(managed-agent): fix the stale step-ceiling header and pin note

The Hosted job's opening parenthetical still summed 291 while the tail,
the Verify step and the job ceiling had moved to 292/202/325. Also say
the replay-floor flag is a default pin, not something currently off.

---------

Co-authored-by: verify <verify@local>
2026-11-02 00:28:04 +08:00
jinyeandQwen-Coder 1bdad7d4b7 refactor(managed-agent): Inject store dependencies through constructors (#16061)
Make dependency references final while preserving optional collaborators, lazy cycle resolution and standalone construction. Migrate verification fixtures and add a synchronized bilingual design.

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-01 16:21:00 +00:00
Shaojin Wen cea333133c fix(managed-agent): correct the child result mirror on consumption (#16074)
* fix(managed-agent): correct the child result mirror on consumption

classify() mirrors the child_run delivery into ma_delivery at
classification time. A parent's consuming revision can land after that
snapshot, leaving a CHILD_RESULT row at `delivered/accepted/pending`
forever while the record itself reads `consumed`; FG10b's
awaitConsumed then times out even though the walk completed.

The parent's own consumption commit now corrects that row in the same
transaction, and classification reads the record row with FOR UPDATE
so a consumption cannot slip between its read and the mirror write. A
row not written yet is left to classification, which reads the
record's current state.

Adds focused H2 coverage for the mirror correction and the commit-path
wiring. Issue: #15744.

* fix(managed-agent): lock only the child run's record row

The classification's locking read matched the session scope without the
record's primary key. MySQL therefore scanned the Session's records and
took next-key locks over the rows that scan touched, so an unrelated
child run's commit in the same Session could wait behind a
classification that had nothing to do with it.

Name the record key in the lookup. A real MySQL IT holds the
classification open and proves the unrelated record still commits; the
same IT fails with the session-scope lookup under a lock wait timeout.
Issue: #15744.
2026-11-01 22:39:10 +08:00
2aa908dc70 fix(managed-agent): block ledger-less agent gap fills and pin the A6 refusal arms (#16015)
* docs(managed-agent): mark the B2 identities and live SSE arm packaged

The G3 recovery-matrix status paragraph still listed B2's attempt/stream
identities and live SSE view as remaining focused/integration evidence after
PR #15993 packaged them into the model-round arm, and its admission-count
clause predated the arm's existing admission audit. Both languages now name
the packaged B2 observations and link the B2 design doc; the remaining list
keeps only A3's rewritten-input/Hook half, A6 and R2.

* fix(managed-agent): block ledger-less agent gap fills and pin the A6 refusal arms

A6's first clause is an evidence-semantics rule: unknown producer evidence
must block, never be laundered into proven absence. Two gaps violated its
evidence surface. The Hook/MCP saved-batch ownership refusal — the only
backstop for the extensions-enabled continuation entry — had zero tests:
deleting its clause kept every existing suite green (demonstrated before
pinning). And the parked-round gap fill answered a stranded agent call with
"the call never ran" when the child ledger was unavailable, although only a
present ledger's missing record is positive never-started proof (admission
commits before dispatch); child-lane config/version drift between generations
makes that arm reachable with a live child.

The fill now throws the family's retriable recovery error for an owed agent
fold with no ledger; journaled answers keep their silent replay and non-agent
calls keep their honest fold. Three ownership cases (hooks, mcp,
hooks+extensions) and four gap-fill cases pin the surface; each deletion
control reds exactly its named case. Focused regression 894/894, typecheck,
ESLint and Prettier clean. Bilingual design doc records the A6 evidence map
and scope boundaries; the parent matrix status notes the landed focused
evidence.

* fix(managed-agent): widen the A6 ledger guard and exempt the terminating routes

R1 review fixes, each verified against the code before fixing:

- The guard covers the whole child-run ledger family (agent || workflow),
  matching the module's own family readers; a stranded workflow call with
  an unavailable ledger no longer receives the fabricated never-ran fold.
- An abort exemption keeps the pre-aborted cancel and interrupted-settle
  fills terminating with their pre-A6 abandonment folds; only the
  live-signal continue route blocks. Without it the guard wedged cancel in
  a deterministic failure loop (regression witness demonstrated at route
  level).
- Two route-level cases build the child-lane drift into the replacement
  load (suppressChildAgents on the version-0 orphan parked shape):
  continue blocks retriably (409) with no fabricated fold, cancel still
  settles.
- The owner-gate matrix drops the vacuous hooks+extensions row (the
  harness entry's extensions clause is recorded as a follow-up) and gains
  both gate directions: a stale-revision commandStep refusal and a
  matching-revision acceptance that keeps the A3 commandStep follow-up
  working.

Nine delete-controls red exactly their named cases on a quiet tree with
known-good baselines; focused regression 8 suites 1583/1583, typecheck,
ESLint and Prettier green. Bilingual design docs name all three fill
callers, the residual trade-off, and the narrowed G2 scope.

* fix(managed-agent): extend the A6 ledger rule to every never-ran writer

R2 review fixes, each verified against the code before fixing:

- The resumed-turn answer (hosted mode) and the command-wait gap fill now
  refuse a ledger-less family call on a live signal, mirroring the agent
  fill; their terminating callers are pre-aborted so cancel and settle
  keep terminating.
- Every ledger-less family fold on the terminating paths — the pre-aborted
  fills and the 'all' settlement — writes a dedicated unknown-outcome
  answer (executionStatus unknown, record retained) that claims nothing,
  never "never ran" and never "cancelled".
- CHILD_LEDGER_TOOL_NAMES becomes the single family-membership fact; the
  six inline two-name literals across recovery and the tool turn now read
  it, so a third ledger-backed tool joins in one place.
- The owner-gate matrix gains the extensions row its comment claimed: a
  Hook owner with extensionConfig and no commandStep is refused, and the
  extensions-exemption mutant reds exactly that row.

Five delete-controls red exactly their named cases on a quiet tree with
failure-list adjudication; focused regression 8 suites 1587/1587, ESLint,
Prettier and typecheck green. Bilingual design doc records the round.

* fix(cli): preserve cancel escape for A6 recovery blocks

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

* test(cli): pin journal-only workflow ledger refusal

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

---------

Co-authored-by: 易良 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-11-01 22:21:47 +08:00
jinyeandQwen-Coder 456a49c14f refactor(managed-agent): Separate runtime broker execution workflows (#16029)
* refactor(managed-agent): Separate runtime broker execution workflows

Give process-local Binding and Session state a registry and separate execution reconciliation, takeover scans and cancellation while preserving the existing fenced state transitions and dispatch boundary. Add a deterministic cooldown completion-order regression and synchronized bilingual designs.

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

* style(managed-agent): Remove extra blank lines from runtime workflows

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-01 13:08:45 +00:00
Shaojin Wenandverify 6e49388c4a docs(serve): refresh daemon usage guidance (#16056)
Replace obsolete alpha-era deployment and persistence limits with current behavior.
Document authenticated background startup, token sidecar lifecycle, and service-manager choices.

Co-authored-by: verify <verify@local>
2026-11-01 17:01:13 +08:00
6bef5a01c6 feat(web-shell): add "Add to Split View" to session menus (R1 fixes for #14878) (#15956)
* feat(web-shell): add sessions to split view from the sidebar

* fix(web-shell): validate split targets and retain pending additions

* fix(web-shell): report transient split-classification failures per session (#14775)

* fix(web-shell): harden the first split open: race, per-id memo, pending mirror, failure notice (#14775)

* fix(web-shell): disable no-op split adds, clarify the capacity hint, document split (#14775)

* fix(web-shell): fix split classification type errors (#14775)

* test(web-shell): fix split pending-navigation and archived-offer test refs (#14775)

* style(web-shell): prettier-format the split classification memo type (#14775)

* fix(web-shell): adapt controlled pane reveal to prop-derived pane set

The main merge (#14577) derives paneIds from the sessionIds prop in
controlled mode, so the reveal effect could no longer sync local state
(setPaneIds does not exist) and its render-time ref check was dead: the
ref is current by the time the effect runs. Track the previous commit in
an effect-time ref instead. Also restore the test-block closer the merge
dropped from SplitView.test.tsx.

* Fix split-persist e2e spec: assert disabled state for already-open session (bot R2 blocker)

The first smoke test clicked 'Add to Split View' on Session A after it
was already a split pane. The R1-10 gate disables that menu item with
the title 'This session is already open in Split View' (sidebar
splitAlreadyOpen), so Playwright retried a click on a disabled item.
Replace the click with toBeDisabled() + title assertions, reusing the
shape of the six-pane spec in the same file. The no-op guarantees
(pane count, draft, DOM persistence) are kept as-is.

---------

Co-authored-by: luozongmin <[email protected]>
Co-authored-by: Сергей Панченко <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
2026-11-01 16:52:54 +08:00
Shaojin WenandQwen-Coder 417e81ec4d fix(review): keep prose re-review requests aimed at the PR (#15998)
* fix(review): keep prose re-review requests aimed at the PR

A re-review request typed as prose ("有更新了,重新评审") carried no
target the parser could classify, and the parser did not refuse it:
every non-flag, non-number, non-URL token classifies as a file target,
so the sentence became a file path, and prose typed beside a PR number
stole the target while the number was demoted to extraTokens.

- parse-args: PR-shaped tokens now outrank file-shaped ones regardless
  of arrival order; an explicit ./<number> spelling stays a file target.
- review skill: a Step 1 intent-routing clause resolves a prose request
  to the PR the conversation names (non-empty inputs only, flags kept,
  the CLI args file preserved), and the description plus the resume
  verdict bullet exclude the updated-PR case from --resume.
- fetch-pr: the head-moved resume refusal now says the fresh run IS the
  re-review and names the plain invocation for next time; the
  restart-bound semantics are unchanged.
- docs: both --resume surfaces in the user guide carry the exclusion.

Pinned by 12 new parse-args cases (the key one red before the fix), a
SKILL.md pin test, and a fetch-pr stderr assertion; DESIGN.md records
the incident as "The prose re-review request".

* fix(review): fence the prose routing's local and posting boundaries

The review round on #15998 caught two defects in the new routing clause
and one regression behind its non-blocking nit:

- Option-only invocations (--fix, --effort low) matched the routing
  condition and would have been retargeted into the conversation's last
  PR, disarming --fix. The clause now names the bare and option-only
  shapes as non-intent and leaves them to the parser verbatim; two new
  parser cases pin that contract.
- The clause promised posting authority for a --comment carried through
  the resolution, but the write gate re-parses the CLI's verbatim record
  on purpose, so a prose record ends at submit with `unbound`. Posting
  is now the resolution's hard boundary: present the resolved invocation
  and ask for an explicit re-run instead of completing a review whose
  submission is already refused. A new submit-suite test pins the
  refusal beside the explicit re-invocation's acceptance.
- The PR-shape reorder demoted a malformed URL behind the assigned
  target and lost its specific diagnostic to the generic extra-argument
  line. The invalid-url refusal is hoisted ahead of the target-assigned
  branch, position-independent, pinned by a dedicated test.

DESIGN.md records the review round; the PR body carries the same
boundaries in both languages.

* fix(review): guard prose routing against local-write intent, qualify scoping

The deep-verification round on ef86b03a99 measured one more hole in the
option-only class and one overclaim:

- F1: "review my local changes --fix" is prose with no classifiable
  target, so it still matched the routing trigger and resolution would
  disarm --fix. The clause now parses the verbatim input first and never
  resolves a PR into a verdict with fix.effective true — a mechanical
  guard, not a wording guess; prose asking for a local review is
  excluded even without --fix. The hash form's copy ambiguity is closed
  too: a # prefix is reference decoration, not part of the target. Four
  new parser cells pin the guard input, the PR-target contrast, the
  hash form, and the ./ escape-hatch boundary.
- F2: delta scoping on a plain re-review is high-only; the docs (both
  surfaces), the resume bullet, the routing clause, the --resume help,
  and the head-moved stderr now all carry the high-effort qualification.
- The ./ escape-hatch wording gains the measured precision: it holds
  when no PR-shaped token is present, not unconditionally.

DESIGN.md records the verification round; the PR body carries the same
boundaries in both languages.

* fix(review): honest resume guidance, promoted-number warning, pre-fix framing

The /review lane's round on 6a5372d6e4 filed five suggestions; all five
verified against the code and addressed:

- R1-1: the routing rationale described the prose-steals-target behavior
  in the present tense — behavior this PR's own parser preference closes.
  It now marks that half as pre-fix and keeps the live half (prose alone
  dies at capture) as the rationale; pinned.
- R1-2/R1-3: the head-moved guidance was gated on a refusal a re-review
  after a completed round never reaches (assessResume checks no-report
  before comparing heads, and cleanup deleted the report), and "gained
  commits" misdescribed a rebase. The guidance now covers the re-review
  family (head-moved, no-report, worktree-gone) with cause-appropriate
  wording; the restart-cost sentence stays with head-moved alone, the
  only reason recordRestart runs for. Docs and the resume bullet carry
  the same completed-round reality.
- R1-4: positive AND negative pins — guidance present on head-moved and
  no-report (without the restart sentence), absent on diff-hash-mismatch
  and worktree-dirty, so dropping the reason gate goes red. R1-3 and
  R1-4 contradicted each other on no-report; the family split resolves
  both (recorded in DESIGN.md).
- R1-5: a bare count inside a sentence is indistinguishable from a PR id
  and the promotion flips --comment to effective. The parser now warns
  when a bare number is promoted with non-PR tokens after it in the
  original order (the canonical number-at-end rescue stays silent), and
  the clause treats the warning as ask-before-running, never posting
  silently. Four-shape test pins presence and absence.

* fix(review): withdraw posting authority from promoted numbers, honest guidance

The sandboxed CI round and the second verification round proved the
promoted-number warning was disclosure, not a fix, and measured four
more gaps:

- R2-1 (Critical): a promoted count carried --comment posting authority
  to a PR the sentence never named ("--comment the 2 follow-ups to
  15998" authorised posting to PR #2). comment.effective is now false
  whenever the PR target is a promoted bare number (promotedNumberTarget
  marks the verdict); the write gate refuses with a promotion-specific
  cause. An explicitly typed number or a PR URL keeps its authority.
- R2-2: the warning moved after the URL preference, decided from the
  actual target, fired exactly when a requested posting authority was
  withheld — demoted numbers and courtesy particles stay silent.
- R2-3: the routing guard keys on what the user spelled, not the
  verdict's target type (prose parses to a file target — the old guard
  excluded the only input it existed for); local-intent moves are
  executable; the hash form has an operational resolution.
- R2-4: trailing sentence punctuation no longer demotes a PR number;
  classification normalises the mark for the integer test only.
- R1-2/R1-3 (fix-induced): worktree-gone gets its own cause-neutral
  sentence (it requires a surviving report); no-report asserts no
  cause; every arm carries the anchor condition; the restart-cost
  sentence prints where recordRestart runs, in the past tense; the
  ruling promise is scoped to high/medium.

New pins: the Critical's exact witness at the gate, both word orders,
the setting-driven arm, worktree-gone, and the reverse-order
malformed-URL cell that reds under a full revert.

* fix(review): name the source of withheld posting authority

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-01 16:50:22 +08:00
Shaojin Wen 0f91cdedae fix(verify-pr): count suite runs and skip notices on closed PRs (#16050)
Follow-ups deferred on #16013:

- A round whose only checks were existing suites had a total of 0
  once suites moved to gates, and the checker refuses that. The skill
  now counts each suite run as one assertion, as it already did for a
  mutant run.
- Gates that record no results (an empty list, or entries counting
  nothing) no longer silence the size warning. When no suite ran, the
  round says in the report what its count holds.
- The blank-suite rule is JSON whitespace on both sides. jq's \S and
  JS's trim() disagreed on U+0085 and U+FEFF, so the checker accepted
  a list the publisher dropped.

Also, a round can queue for hours: the publisher no longer posts a
notice on a PR that closed meanwhile, unless it is finalizing its own
live status. Four such notices landed on #16013 after it merged.
2026-11-01 07:55:09 +00:00
Kun De beaa46b172 feat(web-shell): add conversation activity view (#16022)
* feat(web-shell): add conversation activity view

* fix(web-shell): preserve sidebar attention and activity undo lifecycle

* ci: rerun activity validation after main synchronization
2026-11-01 13:04:08 +08:00
Shaojin Wen 835b080f85 test(autofix-pr): run a watch's helpers in a chain against a fake gh (#16017)
* test(autofix-pr): run a watch's helpers in a chain against a fake gh

Add the integration test the design listed as not written yet. The
helpers run one after another as the skill runs them, through the CLI's
parser, against real git (a bare remote, the user's clone, the watch's
worktree) and a fake gh first on PATH, which they spawn as they would the
real one. The gate runs as `schedule` printed it, split by a real shell.

The fake answers from a JSON description of the PR and reads the PR's
head from the bare repository, so a push moves it. It answers only the
fields a GraphQL query selects, checks the host and the Int PR number,
and logs anything it does not know, which fails the test even when a
helper swallows the failed call.

Five cases: a watch from start to merge (with a reply whose answer was
lost), a push by someone else during a round, a re-run spent then
refused, the per-check fix budget across real pushes (one head's result
asked by commit), and the author's push access. Each also checks the
writes made to GitHub, that pushes are fast-forwards onto the PR's
branch, and that the user's checkout is unchanged.

* test(autofix-pr): check the field behind a GraphQL alias in the fake gh

The fake read an alias's response key and never the field it stands for,
so `root: nonexistentField(first: 1)` was answered like the real
`root: comments(first: 1)`. It now records the field and its arguments
behind each alias and answers only the one alias it renders. The message
for a query it does not answer no longer calls every such query a paging
one.

* test(autofix-pr): pin the conflict seam and the watch's refs in the chain test

- The fake answers `root` only through its alias: selected bare, as
  `root { ... }` or `root(first: 1)`, it is a field GitHub does not have.
  It also refuses a run with no job instead of answering it as a pass.
- The user's checkout is compared with its branches and worktrees too,
  and only `.qwen/tmp` is left out of its status, so a branch or a file
  the watch leaves behind shows. The re-run and fix-budget cases end
  with the cleanup a stop runs.
- A sixth case leaves a conflict for a person: `reply` records it, the
  gate stays quiet on that head and on the head the watch pushes next
  while GitHub still answers UNKNOWN, and someone else's push brings it
  back.
2026-11-01 10:50:41 +08:00
Shaojin WenandQwen-Coder 06c1f98971 fix(verify-pr): count test suites apart from the round's assertions (#16013)
* fix(verify-pr): count test suites apart from the round's assertions

Rounds folded whole workspace suites into assertions.json, so the
published "Scripted assertions" line measured the repo, not the round:
37 of the 252 counted reports in the 30-day census exceeded 1000, and
one round's 7737 held a few dozen checks of its own, the rest being
five suites counted again for every mutant run.

assertions.json gains gates: [{ suite, pass, fail, skipped? }], one
entry per existing test suite run as a targeted gate. The skill says
suite results and mutant runs stay out of pass/fail/total, and that a
gate red the PR causes still reaches the verdict as an assertion. The
checker refuses a malformed list and warns when over 1000 assertions
come with no gates. The publisher prints the gate totals on a line of
their own, numbers only, and does not let them decide the verdict.

Also, deferred from #15938: the paragraph for a merge-ready round with
failures now states the checker's rule instead of inviting the reader
to excuse the failures, and the artifact-check guard's empty-value legs
are pinned.

* fix(verify-pr): warn on empty gates and mirror the checker's shape rules

Address the three stage-2 triage suggestions: an empty gates list no
longer silences the suite-sized warning; the publisher's jq refuses
blank suite names and non-count skipped values (false/null) instead of
defaulting them, so a shape the checker refuses never renders totals;
and the design docs say 'all three' / '三者' where the rule count grew
from two to three.

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

* fix(verify-pr): give the gates shape rule one witness and honest totals

Address the five R1 review suggestions:
- one shared fixture table drives both the publisher's jq and
  checkArtifacts, and both bound counts at 2^53-1, closing the 1e309
  divergence where jq clamped an overflow the checker refuses
- the skipped clause prints only when some entry records the field,
  so an unrecorded count is no longer published as a measured zero
- a trusted verdict over gate reds carries one advisory sentence in
  both languages pointing at the assertion ledger; still verdict-inert
- pin the skill's compensation sentence and name the gates-size
  warning in the local clear-list and the CI-path sentence
- bump the workflow size baseline to 375110: the parity comment, the
  conditional clause and the advisory exceed the remaining allowance

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

* fix(verify-pr): print the skip clause only for fully recorded gates

Address the two R2 review suggestions:
- the clause now requires every entry to record skipped; a mixed list
  published a partial sum as the run's skip total
- pin the advisory's trusted-verdict guard from the false side: an
  untrusted run with gate reds prints the totals but no advisory
- fixtures: a mixed list drops the clause, a recorded skipped:0 keeps
  it, and a timeout headline over gate reds stays advisory-free

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-01 02:26:31 +08:00
易良 eaafba5d14 ci: route the classify_pr gate to ECS for every pull request (#16019)
The gate is the needs-prerequisite of every Linux lane, but its own
runner stayed association-gated: untrusted fork PRs queued the whole
chain on the scarce hosted pool (observed 30+ min on PR #15851).
classify executes only trusted base-SHA code with a read-only token —
a strict subset of the lint exposure accepted on 2026-10-31 — so route
it unconditionally to ECS, kill-switch excepted. The ubuntu_runner
association gate for the test/integration lanes is unchanged.

Routing battery: gate matrix across all associations, structural pin
against clause creep; association-clause mutation reds the suite.
2026-11-01 00:12:22 +08:00
jinyeandQwen-Coder cf7e77810e refactor(managed-agent): Share the session event delivery loop (#15952)
Share Public and WebShell delivery while preserving replay, permissions, timestamp projections, heartbeats and deletion handling. Keep the session creation command already merged upstream and document the focused stream verification.

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-31 23:04:59 +08:00
Shaojin Wen fe30b5daaa test(managed-agent): pin lapsed-lease ledger writes and the IT's result-store wiring (#16018)
* test(managed-agent): pin lapsed-lease ledger writes and the IT's result-store wiring

Design decision 5 says a scanner whose lease lapsed still records and
settles until another claim moves the fence, but no test held that half:
adding a lease_until condition to any ledger write left every case green.
A new AutomationScannerTest case writes past lease_until with owner and
fence unchanged, then lets another claim take the lease.

WorkspaceCsiSqlWorkerMySqlIT now asserts its journal holds the result
store it wired, so dropping that wiring can no longer leave the
zero-projection assertion passing for the wrong reason.

Decision 6 no longer says "lost its lease", which read as expiry.

* test(managed-agent): cover every fenced ledger write after the lease lapses

The lapsed-lease case now also releases, blocks, records a gate outcome
and settles with a detail after lease_until, so a lease_until condition
on any of the nine owner+fence writes fails it.

The scanner's class Javadoc, the ledger's method Javadocs and the sibling
test name said "lost its lease", which reads as expiry; they now name a
takeover by another claim, as decision 6 does.
2026-10-31 22:07:48 +08:00
edde639992 feat(cli): support exact-version updates without npm discovery (#11486)
* feat(cli): support exact-version updates without npm discovery

* fix(cli): repair build and close exact-version update review gaps (#11486)

The merge into this branch left two copies of the semver default import
in standalone-update.ts, so packages/cli no longer compiled (TS2300) and
every downstream gate went red. Drop the added duplicate, keeping main's
import position.

Also address the remaining review findings on the exact-version update:

- Verify the extracted archive's manifest.json version matches the
  requested version before activation, alongside the existing executable
  smoke test. The manifest is the field the installation reads back and
  the prepared-update gate compares against; an inflated manifest version
  would otherwise silently freeze all future prepared updates.
- Pin the yargs builder contract: --target-version must reach the handler
  as argv.targetVersion under the production .strict() parser, going red
  if the builder is dropped or the option renamed.
- Add the issue #11484 acceptance-criterion-7 E2E test: update a
  disposable standalone install to an exact version served from a local
  HTTPS release root while the npm registry sink records zero requests,
  asserting the installed manifest version and rollback retention.
- Document that an explicit target is not persisted: automatic updates
  move a standalone install forward again next session, and keeping an
  exact older version requires general.enableAutoUpdate: false.
- Add the Chinese version of the design doc with reciprocal language
  links.

* fix(cli): close standalone update review gaps from round 2 (#11486)

Address the second-round review findings on the exact-version update:

- Validate the extracted archive's full manifest identity (name, target,
  version) before activation instead of version alone. A mirror artifact
  with the right version but no name passed every check and installed,
  after which the directory silently stopped classifying as a managed
  standalone install; the gate now rejects it and preserves the install.
- Guard both version comparisons with normalizeVersion's own predicates
  (SEMVER_RE and semver.valid), so a regex-valid but semver-invalid
  version like 01.2.3 hits the intended mismatch message instead of
  throwing "Invalid version format" out of the comparison.
- Rename the manifest gate's message to name the archive check rather
  than the smoke test, which has not run yet at that point.
- Make the target-version E2E hermetic: pin QWEN_REQUIRE_SIGNATURE,
  QWEN_CODE_LANG, and QWEN_HOME per spawn (witnessed by staging hostile
  ambient values inside the test), re-seed the fixture installation in
  beforeEach so vitest task retries never inherit the post-activation
  tree, and guard the afterAll server-close promises so an early
  beforeAll failure no longer hangs the hook and leaks the scratch root.
- Pin the documented "explicit targets permit same-version reinstalls
  and downgrades" guarantee with performStandaloneUpdate cases that go
  red if the prepared-route staleness check ever moves into the shared
  path.
- Correct the fixture's header comment: local-git-clone classification
  requires the resolved CLI path inside the cwd, not merely a cwd inside
  a git worktree.

* fix(cli): close standalone archive gate review gaps from round 3 (#11486)

Validate the archive's runtime flavor and executable layout against the
installed build before activation, share one version predicate and one
runtime-layout predicate instead of hand-inlined copies, give every gate
predicate a discriminating test, keep the E2E registry sink honest with a
discovery control, and document the manifest identity contract.

* fix(cli): pin the git shim's module scope in the reverse-apply screen test (#11486)

The test shadows git with an extensionless Node shim under tmpdir. Node
derives the module system of an extensionless file from the nearest
ancestor package.json, so a "type": "module" package.json above tmpdir
(the runner image ships /tmp/package.json) loads the CJS shim as ESM:
it dies on "require is not defined" before forwarding to real git, the
restore's ls-files -v spawn never arms the stamp, and the screen test
fails without the screen ever being asked. Pin {"type":"commonjs"} in
the shim directory so the shim loads the way its body is written,
regardless of what sits above tmpdir.

* fix(cli): flavor-aware standalone archive routing and launcher entry-point gate (#11486)

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

* fix(cli): sanitize --target-version rejection echo and bun test fixture

normalizeVersion throws with the raw flag value interpolated, and the new
--target-version error path echoed it to stderr unsanitized, so terminal
escape sequences (OSC-52, C0/C1, bidi overrides) supplied via argv reached
the user's terminal verbatim. Collapse whitespace and strip escapes,
control and format characters at the write site, mirroring the existing
sanitizeForStderr pipeline, and pin it with an escape-carrying reject case.

Also stage the real bun archive layout in the standalone-update fixture
(bun/bin/bun plus the node/bin/node compat shim installBunRuntime writes)
so the smoke test's two-hop execution chain has a witness: shortening the
staged shim now reds the bun-flavor update test with "Smoke test failed".

* fix(cli): accept pre-v0.23.0 standalone manifests without runtime (#11486)

* fix(cli): address target-version review feedback (#11486)

* fix(cli): keep update recovery guidance and gate win32 test (#11486)

Preserve the updater's manual-recovery command by using a 4,096-character bound for its own failure text; the 500-character bound remains for the untrusted --target-version echo.

Gate the pre-v0.23.0 manifest success-path test with the same win32 skip as its sibling tests, since atomicReplace returns 'deferred' there.

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

* fix(cli): keep update echo sanitizer at its call site (#11486)

Move the bounded stderr sanitizer back next to the update command so the shared stdio helper keeps its existing surface, and raise the hosted plan wait to the 10s bound the rest of its file uses so the full-suite gate no longer times out under load.

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

* fix(cli): raise hosted plan wait to the file's 10s bound (#11486)

* test(cli): describe the registry sink's real enforcement (#11486)

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

* fix(ci): stage the standalone archive layout in the runtime harness (#11486)

applyStandaloneUpdate rejects an extracted archive that lacks bin/qwen and
the bundled runtime, but the harness payload carried neither, so
npm run test:standalone:runtimes:ci died in node/clean before any scenario
ran. Stage the layout create-standalone-package.js ships - bin/qwen plus the
bun flavor's node/bin/node mirror shim - so the fixture matches a real
archive and the update scenarios exercise the gate instead of tripping it.

* fix(cli): gate standalone archives on cli-entry.js (#11486)

The pre-activation layout gate required the installer's launcher and runtime paths, but the repository's standalone-runtime harness repacks a runnable archive without them, so npm run test:standalone:runtimes:ci died in node/clean and the required Test job stayed red. The harness lives in maintainer-only scripts/ territory, so relax the updater's archive gate to the documented v0.18.1 floor: lib/cli-entry.js. smokeTest still runs the bundled runtime, lib/cli.js and lib/cli-entry.js before activation, and isStandaloneInstallDir keeps using the full layout for classification.

Revert the harness edit rejected by the footprint gate, and update the design/user docs and the binless fixture test to the relaxed contract.

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

* fix(cli): make launcher-less standalone updates launchable (#11486)

The update gate accepts archives from the v0.18.1 floor without the
installer's bin/qwen launcher (the standalone-runtime harness repacks one
that way), but installation detection and the PATH wrapper still required
it. A successful update could therefore leave a tree that neither launched
nor classified as standalone.

Share one layout contract between the gate and detection: the CLI entry
point plus the bundled runtime. Detection still accepts the legacy
launcher-only layout. When a tree has no launcher, ensureBinWrapper writes
a wrapper that execs lib/cli-entry.js through the bundled interpreter and
refreshes a stale wrapper that targeted the removed launcher. Update the
design and user docs and cover the classifier, wrapper and refresh paths.

* fix(cli): resolve a restartable launcher for launcher-less standalone installs (#11486)

Accepting launcher-less install trees made the daemon's one-click update
arm a restart against a hardcoded bin/qwen that such a tree does not have:
activation succeeded, the restart threw ENOENT, and the update was
reported as failed. Resolve the PATH wrapper the updater maintains, which
lives outside the install and is refreshed when an archive ships no
launcher, and fall back to the archive's own launcher; offer manual
instructions when neither exists. Only a wrapper that names the install
may restart it.

Skip the launcher-less acceptance test on win32 like its siblings: its
fixture is Unix-shaped and a Windows host derives win32-x64, which
validateTarget rejects.

---------

Co-authored-by: 西岐 <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: qwen-code-ci-bot <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: qwen-code-bot <[email protected]>
2026-10-31 13:42:54 +00:00
86f1338b86 feat(serve): batch workspace session live-state snapshots (#12513)
* feat(serve): batch workspace session live-state snapshots

* test(serve): include batch live-state capability in integration baseline

* fix(serve): close review gaps in the batch session live-state route (#12513)

Index POST /sessions/live-state in the REST reference's persisted-catalog
row so the docs contract sees the protocol section, give the batch route the
single-workspace route's server-side records (routing-failure warn on
untrusted members, daemonLog error on unexpected bridge failures), make the
mid-batch disconnect guard observable with an AbortController and a
macrotask yield between members, and move the duplicated member-failure,
generation-currency, and bound literals into workspace-route-runtime for
both plural batch routes. Six tests pin the new witnesses.

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

* fix(serve): align live-state scope docs and pin batch route guards (#12513)

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

* test(cli): pin grouped REST doc rows against bare SDK names and column shifts

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

* test(cli): fail closed on grouped REST doc rows and ambiguous SDK qualifiers

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

* test(serve): pin documented live-state batch bounds against silent drift (#12513)

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

* test(cli): isolate the suite from the operator's settings home (#12513)

The autofix verification gate runs the packages/cli suite with the host's
real HOME on a machine shared with other jobs. A malformed
$HOME/.qwen/settings.json there (observed content: "X") makes every
operator-settings reader fail closed at once — readOperatorSettingsScopes
is designed to refuse a corrupted policy file — and one such file failed
408 tests across 15 files in the measured gate rejection.

Pin a private empty QWEN_HOME per test-file process in the suite setup
when the environment did not select one, following the file's existing
doctrine for operator-owned environment. Tests that exercise QWEN_HOME
set, stub, or delete it themselves. settings.test.ts keys every
user-settings and .env path off a mocked os.homedir, so it now deletes
an ambient QWEN_HOME explicitly; with any ambient value set, 13 of its
tests fail regardless of this change.

* test(cli): harden the QWEN_HOME suite pin and batch maxima test (#12513)

- Pin on truthiness so an ambient empty QWEN_HOME no longer falls
  through to $HOME/.qwen, matching every production reader.
- Sweep the pinned home from afterAll instead of an 'exit' listener
  that vitest workers never fire (51 leaked dirs on src/config -> 0).
- Restore QWEN_DEBUG_LOG_FILE around loadCliConfig tests so the debug
  logger's fire-and-forget writes stop racing the sweep.
- Witness that the suite never resolves settings from the ambient
  $HOME/.qwen, and assert member shape in the 20-selector maxima half
  so twenty failed members cannot read as twenty snapshots.

* test(cli): keep AppContainer store work off the pinned per-file QWEN_HOME (#12513)

* test(managed-agent): settle fresh group leaders before age-judged sweeps (#12513)

A just-forked process can read in ps with procps's negative-elapsed
wraparound (e.g. 441077234-00:18:40), which parsePsElapsed deliberately
leaves undatable. An undatable leader judges every worker/group identity
'unknown', so an unwitnessed sweep conservatively holds the live process
and rejects unproven — flaking the real-process sweep tests within
milliseconds of the spawn. settledGroupTable now also waits for the
leader's row to be datable, and the four sweep sites that judged a fresh
process without the guard use it.

* fix(cli): keep the QWEN_HOME test pin unconditional and alive for the process lifetime (#12513)

The conditional pin honored an ambient QWEN_HOME, so agent-gate vitest
runs read and wrote the operator's live settings home; the teardown
sweep then deleted the live pin while un-awaited extension-store work
was still reading it, turning Test (ubuntu-latest) red on ENOENT. Pin
unconditionally, keep the pin for the process lifetime, reap stale pins
by age at setup, await the floated extensions-list handler, and add
teardown, pin, reaper, and memory-only-contract witnesses.

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

* fix(cli): keep the QWEN_HOME teardown witness inside test-setup.ts (#12513)

The previous commit registered the pin teardown witness as a second
setupFiles entry in packages/cli/vitest.config.ts, which deterministic
verification rejects as CI/verification machinery outside the PR
footprint. Revert vitest.config.ts and fold the witness into
test-setup.ts itself: vitest runs root-level afterAll hooks in reverse
registration order, so the hook registered immediately after the pin
runs last among this file's root hooks and still turns every file red
if a teardown sweep of the live pin is reintroduced below it
(mutation-probed). The separate witness setup file is deleted.

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

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

* test(integration): raise the installed distribution case ceiling

The tui-parity installed-distribution darwin-x64 leg failed on the
npx-lifecycle-enabled no-relaunch-fallback case with "Test timed out in
120000ms" (run 40537979533, job 122316223603). The five preceding cases
averaged ~117s on that host, right at the ceiling: startup alone can
spend the whole 90s ready-prompt budget there (#15677) and the /about
retry window is another 90s, so 120s cannot contain a healthy case.

Raise the per-case ceiling to 240s and keep the budget helper's note in
sync.

---------

Co-authored-by: 西岐 <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: qwen-code-ci-bot <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: qwen-code-autofix <[email protected]>
Co-authored-by: qwen-code-autofix <qwen-code-autofix@localhost>
Co-authored-by: qwen-code-autofix[bot] <qwen-code-autofix[bot]@users.noreply.github.com>
Co-authored-by: Qwen Autofix <[email protected]>
2026-10-31 21:40:40 +08:00
Shaojin Wen 2a8ae9b2df docs(managed-agent): record the ManagedAgentStore setter investigation (#16014)
* docs(managed-agent): record the ManagedAgentStore setter investigation

Complete the P1 inventory with the ManagedAgentStore finding: the seven
public setters are Spring @Autowired(required=false) optional-dependency
injection (four optional, two required, worktreeFiles manual), not ad-hoc
mutable wiring. Converting them to constructor ObjectProvider injection is
a large change to the central 7,700-line class with ~30 test wiring sites
whose correctness depends on Spring runtime assembly the manual-setter
unit tests do not fully exercise. Like the broker reconciliation cluster,
it warrants a dedicated design pass and full integration verification
before implementation rather than an incremental slice.

* docs(managed-agent): count worktreeFiles among the required setter injections

configureWorktreeFiles carries plain @Autowired and its bean is supplied
by ManagedAgentConfiguration.managedWorktreeFiles(); the direct calls in
VerificationFixtures are test setup, not production wiring. The setter
inventory therefore reads four optional and three required, and the
@Autowired(required = false) statement now applies only to the optional
four. Both language versions move together.
2026-10-31 21:40:05 +08:00
80273bd34a fix(core): extend built-in reasoning effort to DeepSeek-V4 and new Qwen prefixes (#15987)
* fix(core): extend built-in reasoning effort to DeepSeek-V4 and new Qwen prefixes

Third-party gateways (PAI-EAS, internal proxies) host DeepSeek-V4 models
under arbitrary hostnames, and the invitation-only qwen-latest-series
previews and qwen4 family speak the same tiered reasoning_effort contract
as qwen3.8-max. The hostname whitelist and exact-id tables left these
routes without any effort controls.

Recognize the deepseek-v4 prefix case-insensitively on any OpenAI-compatible
endpoint (official endpoint keeps its own contract; non-V4 aliases stay
host-gated), and route the qwen-latest-series-invite- and qwen4 prefixes
through the shared tiered-effort wire gate and the stable qwen3.8-max
control tiers. Explicit capability declarations still take precedence.

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

* fix(core): address review on effort prefix recognition

- Normalize xhigh to high for every v4.1-flash gateway alias, matching the
  stable Flash id instead of falling into the legacy xhigh->max rule.
- Carry the tiered Qwen manifest through opaque duplicate-endpoint routes:
  the route preview guard, the live ACP configuration guards, and the
  reasoning-configuration lookup now admit the new prefixes, with
  two-endpoint regression coverage.
- Stand the built-in DeepSeek normalizer down on newly recognized
  third-party hosts whenever an explicit declaration governs (wire reshape,
  display state, and override clearing), preserving declared/static effort
  semantics; established DeepSeek routes keep their shipped interaction.
- Update the duplicate-route DeepSeek assertion to the new contract and
  refresh the stale single-family scopings in docs and comments.

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

* fix(core): scope declaration stand-down to pre-PR routes, keep routed declarations

- Limit the established-route compatibility exception to the exact
  model/host pairs the pre-PR recognizer accepted, so a declaration for a
  newly recognized alias keeps its wire semantics even on a MaaS host.
- Make the stand-down self-determining: a profile-less resolved capability
  is a declaration, and both the effective-reasoning shaping and the
  DeepSeek state stand the built-in normalizer down on newly recognized
  combinations, covering pipeline injection, provider reshape, display,
  and override clearing alike.
- Preserve an already-resolved declaration in the routed Qwen fallback;
  the tiered manifest applies only when nothing resolved.
- Regressions: MaaS-alias static and configured effort, display/wire
  alignment, established-pair boundaries, and a live ACP duplicate-endpoint
  declaration case.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: 易良 <[email protected]>
2026-10-31 13:17:08 +00:00
Shaojin Wen f99062525d test(managed-agent): pin the private CSI receipt guards and fix design decision 5 (#16011)
#16000 documented that Sessions created through the private CSI lane
record no tool receipts for projection. Pin the two places that hold:

- WorkspaceCsiSqlWorkerMySqlIT now wires a ManagedToolResultStore into
  the journal, as production does, and asserts that no
  TOOL_RESULT_PROJECTION row exists after its receipt-carrying CSI
  commits. Without the store the capture guard was never reached.
- ToolPublicationStoreTest marks the Session private (through
  tool_profile or runtime_request_key, since csi_guard is generated)
  before claiming, asserts the claim is empty and the row untouched,
  then clears the marker and claims the same row.

Decision 5 of the automation runtime design said a scanner whose lease
expired records and settles nothing. Occurrence writes check owner and
fence, never lease_until, so they are refused only once another claim
moves the fence. Both language versions now say so, matching the
AutomationLedgerStore Javadoc corrected in #16000.
2026-10-31 20:28:07 +08:00
Shaojin Wen c726d2cfc1 docs(managed-agent): close the D1 prose finding; record the reconciliation prerequisite (#16012)
Carry forward the R3-1 review fix that was verified but not pushed before
#15997 merged: the D1 structural-debt prose now matches the table (the
observation cooldown and tool-hold inventory are named as extracted
collaborators, not as concerns still mixed into the class), in both
languages, and the service's hold-inventory javadoc defers the detailed
discharge invariant to RuntimeHoldInventory instead of duplicating it.

Also record the reconciliation-cluster investigation: reconciliation,
recovery scanning and cancellation share the service's three concurrent
maps, binding-lifecycle helpers and CAS settle, so none is a clean
standalone seam. The design now names the prerequisite — a shared-state
registry that owns the maps and lifecycle helpers — to be designed and
reviewed on its own before those paths can be extracted, and before the
M6 stamp-before-slot-drop ordering becomes a pinnable seam-level contract.
2026-10-31 19:18:39 +08:00
Shaojin Wen 39fd1b45ba refactor(managed-agent): extract the runtime hold inventory (#15997)
* refactor(managed-agent): extract the runtime hold inventory

Second P1 seam in RuntimeBrokerService: the durable hold inventory moves
to a package-private RuntimeHoldInventory. The pagination over hold rows
and the RELEASED filter (restore the close's drain obligation, never
discharge — #13930 R1-2) move verbatim; the service keeps the admission,
tenant resolution and error-mapping boundary, so the stage assembly and
the runtime_hold_inventory_scope_failed mapping are unchanged. Six new
unit tests cover the collaborator (empty ledger, released discharge,
missing-record-is-not-release, non-released still holds, cursor
pagination, dedup); the 199-test broker suite and the binding inventory
suite stay green.

Recovery scanning was investigated as the next seam and proved entangled
with reconcileInContext, the SessionContext in-memory index and the CAS
settle loop; the design records why it waits for the reconciliation
collaborator rather than forcing a callback-heavy extraction.

* docs(managed-agent): record the hold inventory in the D1 ledger; pin RELEASING

Close the review's D1-table consistency finding: the structural-debt
table's RuntimeBrokerService row now names both extracted seams
(ExecutionObservationCooldown, RuntimeHoldInventory) and refreshes the
line count to the current 6,329, in both languages, so the ledger matches
the slice narrative instead of drifting per extraction.

Also add the round-2 deferred probe as a test: a RELEASING session still
holds (only RELEASED discharges the drain obligation). A widened
discharge filter (!= READY) turns the new test red — verified by
injection with byte-level restore. 224 focused tests and Checkstyle stay
green.
2026-10-31 10:56:14 +00:00
1ed17ff879 feat(channels): expose adapter tools to agent sessions and add a QQ recall tool (#14348)
* feat(channels): expose adapter channel tools to agent sessions

Adapters had no way to hand a platform action to the agent as a client MCP
tool: the channel_loop server only served the built-in loop tools, and its
runtime-add was workspace-scoped, so a reverse frame could not be tied to
one session.

- Add `ChannelToolDefinition` / `ChannelToolContext` and the optional
  `registerChannelTool` bridge method.
- Bind the channel_loop SDK MCP server per session through the
  session-scoped runtime-add/remove, and treat that binding as the
  credential: a `client_mcp/message` frame resolves only for a session this
  bridge bound, so a forged or stale id sees no tools and runs none.
- Scope tools by `owner` so one bridge can serve several channels that
  register the same tool names; a session never sees two tools under one
  name.
- Serve no tools for a frame without a resolved session, and report a
  missing session as an isError tool result instead of a transport error.
- Pass the facade through in the daemon worker and add the opt-in
  `QWEN_CHANNEL_TOOL_DIAG` trace of the session-to-owner resolution.

* feat(qqbot): recall a message through a channel tool

The agent had no way to act on a QQ chat it was answering in: moderation
was operator-only, and the channel never exposed the platform's own
group-management surface.

- Add `requestQQApi`, one thin REST wrapper the channel uses with its own
  token, and the `moderationTools` switch (default on).
- Add `qq_recall_message`: it recalls the message that triggered the turn,
  a named member's most recent message, or an explicit id, and maps the
  platform's refusal codes (not an admin, past the 2-minute window,
  allowlist-only) to text the model can act on.
- Register the tool on the bridge, scoped to the owning channel, and track
  the last inbound message per chat for the 2-minute recall window.
- Document the switch and the tool.

This is the first slice of the group-management surface: the endpoints the
open platform still gates behind an invite-only allowlist are left out.

* test(channels): harden recall and adapter tool assertions

Review follow-ups on the channel tool surface:

- Pin the HTTP method, not only the path, for the member, explicit-id and
  C2C recall branches, and assert the schema properties the model actually
  calls the tool with.
- Assert the calling session id reaches the chat resolver, and cover the
  retry code, the no-status failure fallback and the non-string argument
  error.
- Name adapter fixtures neutrally instead of after tools this slice does
  not ship, and note in the session-id check that the bound-session ledger
  is a membership check, not a transport check.

* test(channels): use the array shorthand for channel tool fixtures

`@typescript-eslint/array-type` rejects `Array<ChannelToolDefinition>` for a
simple type, which is what the Lint & Static job reported.

* fix(qqbot): make the moderation tools opt-in

`moderationTools` defaulted to on, so an existing QQ deployment silently
gained a model-callable destructive action (`qq_recall_message`) with no
operator switch anywhere in the channel config. Register the tools only
when the channel sets `moderationTools: true`, and document the opt-in.

* fix(channels): address the R1 Criticals on channel tool exposure

AcpBridge: retry a skipped channel-loop session binding on the next prompt
(R1-3) and name the failed binding instead of a missing session id (R1-17).
DaemonChannelBridge: judge the per-session adapter tools, not the bridge-wide
table, so another channel's owned tool no longer forces an empty runtime onto
loop-opted-out sessions (R1-6).

QQChannel/moderation tools: make the recall ring first-write-wins so a
redelivered older message cannot become the newest target (R1-2), drop a
recalled message from the ring on success so the next member-targeted recall
reaches the previous one (R1-39), drop a removed group from the ring (R1-20),
reject unrecognized recall arguments instead of falling through to the
destructive implicit target (R1-1), sanitize and cap the moderation log sink
(R1-7), refuse platform calls after the channel is disposed (R1-27), log and
echo the resolved target and its source (R1-16), and name the resolved id in
the retry instruction (R1-25). api.ts: delegate sendQQMessage to requestQQApi
(R1-21) and drop the member-removal claim and its 40062003 clause (R1-8).
Docs: document groupAllPolicy and the member-recall delivery prerequisite,
and the no-argument recall target (R1-15).

Also restore the two closing braces events.test.ts lost in the latest-main
merge, which made the file unable to parse.

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

* fix(channels): require a target for qq_recall_message and make its window configurable

There is no implicit target any more: a call must name member_openid or
message_id, and the tool never recalls the bot's own messages, so every
direct-chat call is rejected as unsupported.

The hardcoded 2-minute window becomes the per-channel
moderationRecallWindowMs option: -1 (default) keeps entries until the
per-chat cap evicts them, 0 disables member-targeted recall (no ring, and
member_openid is rejected while message_id still works), and N > 0 keeps
an entry for N ms. An invalid value warns and falls back to -1.

The ring is no longer recorded when the tools are off or the window is 0,
and all model-visible text and docs stop claiming a platform time limit.

* feat(qqbot): expose the group-management tool suite alongside recall

* test(qqbot): assert the full tool suite registers on the adapter

* fix(channels): bound channel-loop MCP setup and keep bind claims owner-checked

- A stale bind attempt could erase a newer, successful bind's entry in
  channelLoopMcpBoundSessions: a discard during the in-flight runtime-add
  and a reload that binds again leave two attempts for one session id.
  Mint a per-attempt claim next to the optimistic add and release it only
  while this attempt still owns it (dropped wherever the ledger is
  dropped), so a late failure cannot unbind the live session.
- Bound the session-scoped runtime add/remove with
  CHANNEL_TOOL_BIND_TIMEOUT_MS: the add drives initialize/tools/list back
  into the bridge, so a stalled child used to hang session creation and
  every later message for that chat with no stderr line.
- Make QWEN_CHANNEL_TOOL_DIAG diagnosable: print the tool name and the
  JSON-RPC error on the failed-call arm, mark isError for RPC errors, and
  resolve the owner from the carried session id when the session is known
  but unbound (fail closed for ids in neither ledger).
- Pin the named behaviours the round-1 review listed: rejecting
  runtime-add, ordered session creation, loop-tool offering, remove
  failure handling, LRU recall-ring eviction, owner stamping and
  optionsOverrides forwarding.

* fix(qqbot): resolve moderation targets from the current turn

Under a shared sessionScope one session serves every group and DM, and the
router target is frozen at session creation, so a member-targeted recall
could DELETE a message in a chat nobody asked about while reporting
success. Record the turn's chat in onPromptStart (and clear it on
onPromptEnd/onSessionDied/disconnect), and have the moderation tools
resolve their chat from that record: for a per-chat scope fall back to
the routed target with the existing channel-name guard, and fail closed
under sessionScope 'single' when no turn is in progress.

Also stop writing direct-message traffic into the recall ring (entries
were never readable and consumed the shared 256-chat budget), and pin
the turn-chat resolution with a group-A to group-B acceptance test.

* fix(qqbot): harden the moderation tool contracts

- qq_set_mute: require op in both the single and bulk forms (a missing op
  defaulted to the destructive add), forward an explicit empty
  mute_expire_at so op=del lifts a mute immediately, declare item
  required fields, report the mute direction in the result text and the
  log, and reject a non-array members value instead of falling back to
  member_openid.
- qq_kick_member: reject calls naming both target forms, treat an empty
  member_openids array as absent, and rename the blacklist flag to
  add_to_member_blacklist (wire field unchanged).
- Unknown/misspelled arguments are rejected by one shared guard for every
  tool, with additionalProperties: false on all 18 schemas.
- Member-targeted recall requires the turn's own sender unless a
  message_id is supplied; reserved endpoints are marked in the
  model-facing descriptions; the 40062003 explanation is only offered for
  member-targeted tools; a recall whose platform response was lost drops
  its target and echoes the message_id.
- qqbot docs: name the ring's 256-chat whole-ring sweep as the second
  bound on moderationRecallWindowMs, restore the non-@ qualifier, and
  document the sender rule.

---------

Co-authored-by: Eric-GoodBoy-Tech <[email protected]>
Co-authored-by: Qwen-Code <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
2026-10-31 10:36:21 +00:00
jinyeandQwen-Coder 4cd3372bb6 fix(managed-agent): close post-split verification gaps (#15927)
* test(managed-agent): close post-split verification gaps

Establish an active response before probing caller cancellation, retain close deadlines, run both independent embedding hosts from the full MySQL lane, and record post-merge acceptance boundaries.

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

* ci(managed-agent): keep embedding checks within workflow size gate

Move the existing embedding-host verification commands unchanged into a shell script so sdk-java.yml stays within the existing growth allowance. Extend the workflow guards to inspect the script and retain the test and cache-lock requirements.

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

* fix(cli): finish hosted settle budget call migration

Replace the 25 remaining references to the removed settle timeout constant with the existing per-test budget function. This repairs the TypeScript compilation failure exposed by the main merge while preserving the new shared deadline policy.

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

* fix(managed-agent): preserve Hosted fault checks after module split

Preserve the typed stream-idle failure and its duration across the SDK boundary. Update fault-injection fixtures for wrapped connectors and the verification module directory, and wait for the documented pending Items projection within the existing bounded poll.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-31 18:31:00 +08:00
d415444b78 feat(web-shell): add session Workflow mode controls (DW-04B) (#15779)
* feat(web-shell): model Workflow mode and guard config ordering

* feat(web-shell): synchronize Workflow mode with its session owner

* feat(web-shell): expose Workflow controls in Web and IDE composers

* test(web-shell): verify Workflow controls across browsers and real sessions

* fix(web-shell): preserve reasoning preferences during config reconciliation

---------

Co-authored-by: qqqys <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
2026-10-31 18:24:29 +08:00
Shaojin Wen 43415ed8a4 docs(managed-agent): note the private CSI receipt exception and correct lease wording (#16000)
The artifacts.enabled row said committed tool receipts are always
recorded for projection. Sessions created through the private CSI lane
(csi-session) are the exception: ManagedSessionStore records receipts
only when the commit has no CSI original, and the projection claim skips
csi_guard Sessions as well.

The automation.lease row now counts the lease from the claim of each
definition rather than from a scanner tick, matching the scanner-threads
row. The AutomationLedgerStore Javadoc no longer says an expired lease
alone stops a writer: its writes check owner and fence, so they are
refused only once another writer claims the definition.
2026-10-31 18:14:56 +08:00
ChiGao 6818c0233b fix(cli): request consent for native extension commands (#15947)
* fix(cli): request consent for native extension commands

* test(cli): provide extension manager in voice shell fixture
2026-10-31 17:59:19 +08:00
Shaojin Wen c262f8e767 test(managed-agent): gate the B2 attempt/stream identities and live SSE view (#15993)
* test(managed-agent): gate the B2 attempt/stream identities and live SSE view

The model-round arm already proved reissue parity, retraction ordering and
admission across two replacements, but not the B2 matrix row's identity and
live-view observations: a recovery reusing a dead generation's attempt or
stream identity, or a control plane leaking orphaned prefixes to a connected
client, would pass CI.

Extend the existing harness-only model-round arm (no new CI step, no serve
hook, no production change) with:

- Attempt identities: three distinct journal attemptIds, one new attempt per
  streaming generation, checked before the shared audit so a reused id (which
  collides the journal command ids and dedups later attempts) is named as the
  root cause, not the downstream text leak.
- Stream identities: the public identity model verified on the real stack is
  a stable per-Turn assistant item_id plus a content Part that a delta
  continues only while the previous delta keeps its identity; retraction
  blanks the dead deltas' identities, so each replacement must open a new
  Part. Assert pairwise distinct Parts across the two dead prefixes (live
  capture) and the surviving answer (reload), one shared assistant item, and
  blanked prefix deltas in reload AND replay.
- Live SSE view: one runner SSE client connects before the first prefix and
  stays open across both Harness crashes (the stream is control-plane-owned);
  it must observe both prefixes, both retraction announcements and exactly
  one terminal, and its converged view (deltas after the last retraction, per
  the payload-less stream.reconciled reload contract) must equal the REST
  reload and a Last-Event-ID: 0 replay. Per-phase liveness guards keep every
  observation non-vacuous.

Real-stack (.75) evidence: head green three times, including the final runner;
M1 (deterministic attemptId) killed at the named attempt-identity assertion;
M2 (retractOriginal short-circuit) killed at the B2 retraction-ordering
observation with both dead prefixes leaking; M3 (constant messageId) red at an
upstream publication barrier, recorded honestly as an upstream-guard
observation rather than a B2-specific witness. The SSE parser, identity
readers and gates carry behavioural tests and contract pins; a self-audit
fix aborts pending SSE captures in the main finally so an early failure exits
promptly.

* test(managed-agent): harden the B2 arm per review (R1-1..R1-10)

R1-1 (Critical): the teardown abort rejected the never-awaited live-capture
promise, so an early arm failure died mid-cleanup as an unhandled AbortError.
Mark the capture handled at creation (the audit's await still sees the real
rejection), rewrap abort/budget errors as B2 live SSE, and abort observers in
the main finally. Behavioural abort-semantics test plus a source pin.

R1-2 (Critical): "exactly one terminal" could never fail — the capture returned
on the first terminal frame. The terminal is now a progress signal: a bounded
drain window (drainMs, default 2s) keeps reading, and the audit counts terminals
of the original turn over the whole capture. Behavioural double-terminal test;
deleting the drain reds it (verified).

R1-3/R1-4: every identity group must actually carry the shared assistant item
(a null-item generation no longer passes via the union), and the replay matches
the surviving answer's content Part — the stream identity — not just the
per-Turn-constant item_id.

R1-5: drive the shared SseObserver (content-type, id===sequence, event===type
wire assertions) instead of a third private parse loop; the split-frame witness
now exercises the shared observer directly.

R1-6: lift the view audit into top-level assertB2StreamViews so behavioural
tests execute every clause (double terminal, shared Part, missing item,
blanking, replay Part, converged text); deleting the item check reds the
missing-item case (verified).

R1-7/R1-8: one shared readJournalManagedEvents HEX decode for all four journal
readers; one shared textDeltas selector (deltaSequences keeps its empty-match
throw, b2DeltaIdentity tolerates it).

R1-9/R1-10: design docs (both languages) name the shipped helpers/signatures;
evidence goes through the shared writeEvidenceFile into an explicitly named
model-round-<epoch> directory, never a borrowed packaged-undefined one.

Real stack: .75 head green with the refactored runner; M1 re-run confirms the
early-failure path now reports the B2 attempt-identity error and completes
teardown. Contract suites 138/138, typecheck and lint green.

* test(managed-agent): audit the B2 live answer identity and replay terminal type

F5 (P2): assertB2StreamViews extracted the live answer's identity but never
checked it — a live stream delivering the surviving answer under a dead
generation's Part, with no identity, or under a foreign item passed as long as
the persisted views were correct. The live answer must now carry an identity
equal to the reload answer's item AND Part. Three live-only counterexamples
(dead Part, null identity, foreign item) plus a verified delete-control.

F6 (P2): the replay branch counted terminals but never checked the type — a
turn.failed/turn.cancelled replay with the same turn id, text and identities
passed, so a reconnecting client could be shown a failure while live/reload
show success. The replay terminal must now be turn.completed; two
counterexamples plus a verified delete-control.

F7: the design acceptance criterion claimed a named mutation red for every new
signal while the same doc records M3 as an upstream-guard observation. Both
languages now name the evidence per signal (M1 named, M2 joint witnessing, M3
upstream-guard) and list the view-audit clauses with their counterexamples.

Adversarial completeness: five more counterexamples (live failure terminal,
missing earlier prefix, single retraction, misordered retraction, unblanked
replay prefix) and readPublicSse now wraps every failure path (observer
failure, abort, incomplete capture) with a B2-prefixed error.

Verified: the four F5/F6 counterexamples fail against the pre-fix helper and
pass after; deleting either new clause reds its cases. Contract suites 138/138,
typecheck/lint/Prettier green, .75 model-round head green with the new runner.

* test(managed-agent): pin every B2 audit clause and de-flake the SSE wait stub

The CI Test lane reddened on the readPublicSse behavioural test: the injected
waitUntil stub budgeted iterations while the drain predicate reads the wall
clock, so a fast runner exhausted 10k setImmediate turns before the 100ms
drain window ended (3/3 retries, same signature). The stub now budgets wall
time like the real waitUntil; the drain delete-control still reds the named
double-terminal case.

Close the verification report's S1 coverage gaps: the 11 audit clauses that
survived single-clause deletion (converged live/replay text, first retraction
ordering, shared-item across prefix groups, second reload prefix blanking,
replay terminal count, missing replay prefix frames, replay answer item, and
the observer-failure / incomplete-capture catch branches) now have permanent
behavioural counterexamples, each verified to red when its named clause is
deleted. Both design languages distinguish load-bearing clauses (delete-
control verified) from redundant-defence clauses (sibling-covered by design).
2026-10-31 09:56:45 +00:00
Shaojin Wenand易良 22c944ef3c feat(autofix-pr): leave a check for a person once three fix pushes left it red (#15985)
* feat(autofix-pr): leave a check for a person once three fix pushes left it red

A watch fixed a failing check again on every new head, so a check the fix
agent could not make pass was bounded only by the ten pushes per watch.
Prepared snapshots now record each check a round saw fail and has not seen
pass since, and count the watch's own later pushes it failed on. After
three, the check's ci_failed carries exhausted: true and the skill answers
it as needing a human instead of fixing it; the watch keeps going for
review feedback and the other checks.

The gate wakes no round for a pass, so when a recorded check fails on a
later push of the watch, the snapshot asks GitHub what it ended in on the
watch's pushes in between, and on the head it last failed on when a round
re-ran it there: a pass there starts the count over. Someone else's push
gives every check its pushes back, in sync when it adopts one; a return to
an earlier push of the watch spends nothing; only a read of the head the
watch knows counts, and only a read that changes the record takes the
watch's lock.

* fix(autofix-pr): keep unanswered heads for the fix budget and skip cut rollups

A read whose head rollup is cut at the page limit no longer changes the
fix budget: a later run of a check may sit on the pages not read, so a
visible old failure could spend a push and a visible old pass could
drop the record.

A head the count asked about and got no result for (a re-run still
going, a cancelled or unlisted run, GitHub not answering, or a head past
the lookup budget) now stays on the record, the newest four, and is
asked again on the record's next push. A pass found there later leaves
only the pushes counted since a round next saw the check fail; a
failure found there spends nothing.

A pass a lookup finds now ends the record as a pass a round reads does,
so the failure after it is a first one again, instead of restarting the
count and still spending for the failing head and the failures in
between after the pass.

prepare now records the head through withKnownHead, so recreating a
worktree at someone else's push gives every check its pushes back at
the write, as sync does.

* fix(autofix-pr): keep only heads that may still answer, and take review follow-ups

The fix budget now tells a head whose check is still running from one
where it ended with no result. A check run not completed, a pending
status, a re-run GitHub has not listed yet (no check run after the floor
rerunChecks recorded), or a check not listed while other checks of the
commit still run is pending, and stays on the record with heads GitHub
did not answer about. A cancelled or skipped run, or a check not listed
once the commit's checks have all ended, is dropped, so it cannot hold a
slot or a lookup forever. Before, a re-run not listed yet was read as
having failed again, and its later pass was never asked about. A re-run
only held for a run still going is not asked about: it starts on its own
head only, which the counted push has left.

Each read asks every record's re-run, then its pushes in between newest
first, before any head it asks about again, so a re-ask never takes the
lookup a push in between needs. A record keeps at most as many
unanswered heads as a watch has pushes, so a re-run head is no longer
the first one dropped. A round seeing a head fail on a return to it
drops that head, so a late pass after a rollback counts as an answer at
once would.

Review follow-ups: the stale-base design names the exhausted condition,
the lock paragraph lists snapshot, the Section 10 row and the action
precedence say a check out of fix pushes leaves the watch running, and
tests now pin the sync --discard reset, the stale-base hint beside a
spent check, the per-identity exhausted mark, both early returns under
the lock, and the order of records a read leaves as they were.

---------

Co-authored-by: 易良 <[email protected]>
2026-10-31 09:19:46 +00:00
Shaojin Wen f5e5736240 test(managed-agent): unwrap the connector decorator in the Hosted attachment lookup (#15984)
* test(managed-agent): unwrap the connector decorator in the Hosted attachment lookup

The shared lookup reflected "attachments" straight off the HarnessConnector
bean, but that bean is now a decorator: the wiring returns
new SdkHarnessBoundary(connector), and the map lives on the wrapped
QwenHostedHarnessConnector. Every Hosted gate that asks for a Session's
attachment therefore died in the test-support layer with
IllegalArgumentException before the row under test ran, which is most of
the new failures in the nightly Hosted fault lane.

Walk the delegate chain to whichever connector owns the map, and share that
lookup with the one other site that reflected the same field off the Spring
bean. The walk is by field shape rather than by naming the decorator, so a
future wrapper does not reintroduce this; a chain that owns no map still
falls through to the original Spring error, which names the missing field.

Pinned in the default surefire lane, where the shared helper already has a
test, using a doubly wrapped connector so a single unwrap would not pass.
Removing the walk reproduces the nightly error verbatim.

Refs #15972

* test(managed-agent): resolve the connector owner once for every private read

The first revision unwrapped only the attachment map, so the gate row got
one line further and then died reading "client" off the same decorated
bean. Lift the walk into a shared owner resolver and route both reads
through it, which is the shape that keeps the next private read from
repeating this.

Match fields by name plus assignability rather than findField's type
argument: measured against the resolved spring-core, that argument matches
the declared type exactly, so a delegate declared as a concrete connector
would be missed. A field that exists and holds null is still found, which
the volatile client field relies on.

Scanned every site that resolves the connector bean, not just the flagged
one: these two are the only private-field reads left on it. The rest call
contract methods the decorator forwards, or use the fixed lookup.

Refs #15972

* test(managed-agent): pin both defensive branches of the connector owner walk

Review showed that neither branch was exercised: swapping the assignability
check for an exact type comparison, or deleting the null-delegate stop, left
the whole class green. Both edits are silent regressions — the first makes a
decorator with a concrete-typed delegate look like a chain leaf and puts the
attachment lookup straight back into the failure this PR removes, and the
second turns a chain with no owner into a NullPointerException instead of
the error naming the missing field.

Pin each with its own case: a test decorator declaring its delegate as the
concrete connector, and a chain ending in a connector that owns neither
field. Each mutant now fails exactly one test, verified separately so the
attribution is not shared.

Also measured what the null-owner guard leads to: getField(null, name)
raises IllegalArgumentException naming the missing target, not an NPE.

Refs #15972

* test(managed-agent): resolve the pool gate's connector and name the field on a null chain

Verification carried two findings against the previous head, and review
deferred a third.

The pool gate asked Spring for the concrete HarnessPoolConnector, but the
wired bean is the boundary wrapping it, so the lookup found no bean and the
nightly row died before reaching its own assertions. Route it through the
shared owner resolver instead of unwrapping one level locally: the resolver
already stops at a connector that owns no attachment map, which is exactly
the pool's shape, and a second copy of the walk is what this change exists to
remove. That needs the resolver visible from the harness subpackage, so the
support class and that one method become public; the design record says so in
both languages.

A delegate that is itself null ran the walk off the end, and getField(null,…)
then complained about the missing target instead of naming the field the
caller asked for. Fall back to the connector we started from.

Replace the null-client round-trip assertion with a case that can actually
fail. The walk never reads field values, so nulling the client could not
change its answer; the new case pins the null-delegate fallback instead, and
reds with the target-complaint message when the fallback is removed.

Refs #15972
2026-10-31 17:02:32 +08:00
ca7d487ce9 ci: route Lint & Static to ECS for every pull request (#15974)
* ci: route Lint & Static to ECS for every pull request

* ci: isolate HOME and complete routing pins for lint_and_static

- lint_and_static admits untrusted forks to the shared pool, so pin a
  job-level HOME/USERPROFILE under runner.temp and create it before the
  first write (git safe.directory, npm config).
- Derive the classifier output from each job's own runs-on in the ceiling
  helper, so a timeout pointed at the wrong output throws instead of
  being substituted; parameterise runnerOn the same way.
- Pin test's own runs-on to ubuntu_runner and add the HOME-isolation
  guard for every lane routed past the association gate.
- Correct the qwen-autofix premise and the integration_no_ak comment,
  and record the routing decision in a bilingual design note.

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

* fix(ci): isolate lint lane HOME and restore routing guard strength

Resolve R1-1 (Critical): per-job HOME for lint_and_static so untrusted
fork code cannot plant git/npm config a later trusted job inherits;
shared warm caches re-pointed via getent + XDG_DATA_HOME/COREPACK_HOME.
Resolve R1-2: timeout ceilings derive the output name from each job's
own runs-on. Resolve R1-3: explicit ubuntu_runner pin for test.

* docs(ci): record lint ECS fork-routing design and correct stale premises

Resolve R1-5: bilingual design record (container tradeoff, acceptance,
residual risks), qwen-autofix.md section 38 premise, no_ak scoping comment.

* fix(ci): publish run-unique isolated HOME from step env, not job env

Resolve P1/R1-1: jobs.<job_id>.env cannot read the runner context, so the
workflow failed GitHub validation entirely; publish HOME/USERPROFILE via
GITHUB_ENV from the first step instead. Resolve R2-1: RUNNER_TEMP survives
across jobs on this pool, so the path is run-unique, cleared before create,
and reclaimed by an always() step before the failure collector. Resolve
R2-3: share content-verified linter/corepack caches via RUNNER_TOOL_CACHE.
Resolve R2-5: move the HOME guard into the PR-gated routing battery with a
single-lane set pin; resolve wenshao R2-4 with one fleet ladder for both
outputs. Witness: actionlint 1.7.12 exits clean on ci.yml; six mutations
each red the matching guard.

* docs(ci): correct lint routing record per round-2 review

Supersede link points at qwen-autofix.md section 38 (R2-4); read-only token
and HOME-relative write class stated precisely (R2-2); residual risks are
examples not a census, naming web_shell_e2e_smoke and other PAT pool jobs
(wenshao R2-3); association gate recorded as throughput policy, not an
enforceable boundary (R2-6); dispatch-over-fleet priority and per-lane
hosted ceilings (P3); cache cost list and PR-gated test placement updated.

* chore(ci): bump ci.yml size ratchet for merged tmpdir routing and HOME isolation

Growth is real: #15979's helper-test tmpdir routing arrived via the main
merge the freshness gate required, plus this PR's run-unique HOME
mechanism. 156377 -> 161281 bytes.

* fix(ci): keep COREPACK_HOME per-run; shared cache is a poisoning channel

Corepack does not re-verify extracted entries on cache hit (reproduced
locally on 0.36.0 with the repo's pnpm pin: tampered entry executes with
the recorded hash unchanged, network disabled). Move COREPACK_HOME inside
the run-unique HOME; XDG_CACHE_HOME stays shared because lint.js
SHA-256-verifies linter archives on every hit. Guard and bilingual record
updated; shared-corepack mutation reds the guard.

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
2026-10-31 16:35:58 +08:00
11b40836d3 fix(ci): exempt non-TUI packages from the parity impact gate (#15937)
* fix(ci): exempt non-TUI packages from the parity impact gate

* fix(ci): exempt the manifest-less SDK trees from the parity gate

The exclusion list was audited from workspace manifests, so
packages/sdk-java, packages/sdk-python and packages/zed-extension — none of
which carries a package.json — stayed outside it and still selected the
component lane for a PR touching only them.

Add the three trees to the exclusion, pin each with a single-file case in the
classifier contract suite, and record why they are hand-listed in the design
doc pair.

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

* fix(ci): name the policy exclusion and cover the CUA driver tree

The exclusion branch skipped every matched path before recording a
reason, so a PR excluded on policy reported the same "No TUI impact"
as a PR whose paths the classifier never mapped. Record the reason once
inside that branch (R1-2).

packages/cua-driver is outside the --cli-only closure: node-repl
declares @qwen-code/cua-sdk, but the build filter pulls node-repl
without its dependencies and no TUI suite imports the SDK. Exempt it
with a single-file row and a rename row (R2-1).

The workspace-excluded trees were a third hand-maintained copy of the
set pnpm-workspace.yaml states, with nothing reconciling them. A new
case reads pnpm-workspace.yaml and asserts every excluded tree still
classifies as no-impact (R2-2).

The design docs claimed the excluded packages are never built by these
gates. npm run bundle compiles integrations/external-context into
dist/mem0 through hard-coded esbuild entries, so say what actually
happens and name esbuild entries and copy_bundle_assets copies as
inputs the manifest audit cannot see (R2-3).

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

* test(ci): reconcile workspace negations by value, not literal spelling

* test(ci): pin the policy reason for every excluded workspace tree

R4-1: the reconciliation test only asserted outputs.component, the value the
classifier starts with, so narrowing reasons.add(...) for any tree except
cua-driver silently reverted the human-facing diagnostic to "No TUI impact"
while all 163 tests stayed green.

- assert outputs.reason inside the loop, not only for the CUA driver tree
- extract excludedTrees(yamlText) and feed it a fixture with a trailing-comment
  negation and an !integrations/... negation, so the by-value derivation is
  exercised for the spellings its comment claims

Witness (both mutants, unmutated suite 163/163 green):
- reasons.add narrowed to packages/cua-driver/: reason assertion red
- derivation reverted to the old literal regex: fixture assertion red

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

---------

Co-authored-by: Shaojin Wen <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-10-31 15:49:57 +08:00
Shaojin Wen 7ae83e6464 refactor(managed-agent): extract the execution observation cooldown (#15986)
* refactor(managed-agent): extract the execution observation cooldown

First P1 seam in RuntimeBrokerService: the UNKNOWN-lookup cooldown moves
to a package-private ExecutionObservationCooldown that owns the only state
it touches (the cooled-entry map), shares the service's clock and
scheduler, and has no lifecycle of its own. The lookup read path and the
stamp/eviction path move verbatim, comments included; the service keeps
the stamp-before-slot-drop ordering in lookupOnce. The window constant
moves with the logic and the test-access seam follows it.

Behavior is unchanged: the 199-test RuntimeBrokerServiceTest suite and
the 43-test HTTP server suite stay green, plus six new unit tests for the
extracted class (settled replay, fresher-UNKNOWN merge, expiry, same-clock
eviction proven by rewinding the clock, and the closing-scheduler path).
Recovery scanning, cancellation and tool holds follow as separate slices.

* test(managed-agent): make the cooldown eviction tests deterministic

Address the review of the first P1 seam. The eviction test slept 1.3s
against a real scheduler and assumed the 1s task had fired; a delayed
worker would rewind the clock first and fail the test while production
is correct (reproduced by the reviewer with a latched executor). Capture
the scheduled task in a test scheduler and run it explicitly after
advancing the shared clock — no wall-clock race — and add the converse
test: an eviction whose clock has not moved must keep the entry.

Also shut down every test executor (@AfterEach plus try/finally for the
capturing and closing schedulers), fixing the leaked non-daemon workers.
The focused verification now includes the Issue13183 regression and
adversarial suites (25 + 6 tests), all green alongside the 7 unit tests.

* test(managed-agent): pin the cooldown's value-equal remove and schedule delay

Close the three review suggestions on the first P1 seam. A new test
stamps the same execution twice inside one window and fires the earlier
eviction after its own deadline: the two-argument remove must leave the
fresher entry, killing the one-argument mutant that would restore the
issue-#13183 fan-out for the restamped window's second half. The
capturing scheduler now records the schedule delay and unit, asserted
against the moved WINDOW constant, killing a 60-second-delay mutant that
would silently grow the entry map. The D1 inventory note that called the
broker module single-class is corrected in both design languages.

The stamp-before-slot-drop ordering gap the verification report flagged
is pre-existing (identical at base) and is recorded in the design as a
known gap to pin when the reconciliation path becomes its own
collaborator; a deterministic fixture today would need reflection into a
private lambda's internal sequence.

282 focused tests and Checkstyle stay green; both new mutants verified
killed with byte-level restore.
2026-10-31 15:42:39 +08:00