mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
2eee9b7186beb975c7d47bf5d67996cfdcd112c4
2334
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
26c3fb7a5a |
feat(core): add a convergence reminder for read-only exploration (#13601)
* feat(core): add a convergence reminder for read-only exploration
Share an advisory exploration allowance across core and daemon tool loops without adding a halt or changing tool permissions.
Co-authored-by: Qwen-Coder <[email protected]>
* fix(cli): keep halted tool batches out of the exploration budget
recordDaemonToolCalls fed the exploration budget at the top of its
per-call loop, before the per-turn cap and global-duplicate checks. A
batch that those checks halt is skipped whole and never executes, yet
its calls still counted toward the budget and still resolved every tool
name through the registry. Move the recording after both halt checks so
only calls the turn actually runs feed the budget, and a capped turn
performs no registry lookups.
Also complete the client-goal Config double with getMaxToolCallsPerTurn
and getToolRegistry, which the new budget call sites require.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-conflict/jmuy5teh2cj
* fix(core): roll back the exploration budget and turn cap on ModelFallback
A model fallback restarts the attempt from scratch exactly like a retry
(Turn clears pendingToolCalls for both), but the exploration budget's
rollback and LoopDetectionService's per-turn cap rollback both fired on
Retry only. The discarded attempt's read calls stayed counted, inflating
the phase counter by every failed hop of the fallback chain, so the
single reminder (and the cap) could fire before the executed evidence
reached the allowance — the same drift the daemon path avoids by
recording only the surviving batch.
Wire both rollbacks to ModelFallback beside Retry, and pin the core-side
budget wiring (record/commit/rollback/reset, the session-disable gate,
and the duplicate-call-id dedup) with client integration tests that
drive real tool-result continuations; each guard was verified to fail
under its targeted mutation.
Co-authored-by: Qwen-Coder <[email protected]>
* style(core): apply prettier to the new convergence-reminder tests
The Lint lane's whole-repo prettier check was short-circuited by the stale
freshness gate, so these lines were never formatted. Formatting only.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-conflict/jmv2f2dk80w
* fix(core): keep guard evidence across ModelFallback and close review gaps
The review-pr round on
|
||
|
|
17fe37cdc1 |
feat(managed-agent): add L4 Shell and MCP session lifecycle (#13931)
* feat(managed-agent): add Shell and MCP lifecycle v2 * fix(managed-agent): enforce exact lifecycle profiles after main rebase * fix(managed-agent): reject new model attempts throughout lifecycle v2 * test(managed-agent): verify public inline capture collection * fix(managed-agent): verify foreground lifecycle capture evidence codex: fix CI failure on PR #13931 * fix(managed-agent): verify original Shell capture input digest codex: fix CI failure on PR #13931 * codex: address PR review feedback (#13931) |
||
|
|
75470716ba |
test(managed-agent): land the deferred findings from the definition-execution review (#13980)
* test(managed-agent): land the deferred findings from the definition-execution review Seven review Suggestions deferred from #13530 by its size fuse: 1. Design §5.3 claimed Shell profiles must pair with the 'default' approval mode, but the shipped compiler refuses only 'yolo' (three value vocabulary). Align both the EN and zh-CN §5.3 bullet with the combination rule and decision 5 the code already implements. 2. §8 lists 'no partially updated read under a concurrent update' as a verified property, but the FOR UPDATE read had zero test references. Add a two-thread H2 test holding the row lock through commit; the contender's locking read blocks until the holder commits. Reverting either FOR UPDATE to a plain read turns it red. 3. The instructionsRef guard's kind and schemaVersion legs were unexercised; add both negative cases next to the byteLength one. 4. The stored-definitions test was the file's only top-level it(), so no describe-scoped beforeEach ran for it and an isolated -t run wrote journals into the vitest CWD. Give it its own describe with temp-root setup and teardown. 5. The credential-failure half of the model_unavailable promise had no automated witness: a matched, unambiguous model whose switchModel rejects must fail the Turn before inference with zero requests. 6. The W1 recovery branch's 64 KiB cap and fatal UTF-8 decode were unpinned; the only negative case tripped the generic digest check. Add an over-cap row and an invalid-UTF-8 row, both with self-consistent digests. 7. metadataNeverAffectsExecution planted a metadata mode identical to the deployment mode and expected result, so it could not observe metadata being ignored. Plant a differing literal. Fixes #13840 * test(managed-agent): drive item 2's concurrency witness through the production read The stage-2 review found the item 2 test exercised H2's FOR UPDATE semantics through inline SQL rather than the store method it was named after — swapping production's latestForUpdate for latest kept the suite green, so the witness did not cover the code under review. Both threads now run the production store read inside a transaction over the shared DataSource bean: the holder takes latestForUpdate's row lock and holds it through a 300ms window, the contender's identical read must block on that lock for most of the window. Dropping FOR UPDATE from the production query turns the test red (verified by mutation); eight consecutive runs stay green, and the assertion is on the blocked duration rather than cross-thread nanosecond ordering, so the commit boundary cannot race the assertion. Also takes the review's non-blocking suggestion: the item 3 kind/ schemaVersion cases and the item 6 invalid-UTF-8 case now assert the throw messages, matching the over-cap case's existing form. |
||
|
|
415726c28c |
fix(cli): window the OpenTUI transcript to fix the blank-screen resume (#13669)
* fix(cli): window the OpenTUI transcript so a long resume cannot blank the screen @opentui allocates one native TextBuffer per mounted text element, and the OpenTUI transcript mounted every history item at once. Resuming a session of a few thousand records exhausted the process, and the resulting throw was silent: it lands inside a React host-instance creation, @opentui/react's own boundary catches it, its red-text fallback calls a jsxDEV the bundle does not provide and throws too, and React unmounts the whole root, leaving an empty alternate screen with a live process still accepting input. Mount only the items near the viewport, budgeted in rows rather than items, with spacers standing in for the rest so the scroll geometry still spans the whole session. Estimates mirror the values the ink transcript already uses, real heights are measured per item id and survive unmount, and the scroll position is sampled on the renderer's frame event so an idle terminal costs nothing. Also wrap each mounted item in an error boundary whose fallback renders nothing. A failed item now costs one row instead of the whole UI, and the error reaches the OPEN_TUI_TRANSCRIPT debug log. The session picker's preview pane is clipped and does not scroll, so the view takes an initial anchor and the preview passes 'top'. * fix(cli): stop the transcript window moving the reading position Three defects in the windowing's scroll handling: The height table was cleared on a width change, which renumbered every row offset underneath a scroll position that is itself counted in rows, so a resize moved the visible turn by more than a hundred. The entries a resize actually invalidates are the mounted ones, and the next frame re-measures those. A height correction was written back through the sticky-aware scrollTop setter. No correction is owed: an item the measuring loop can see is already painted at the height just read from it, and both spacers derive from offsets a change inside the window leaves alone. Writing one moved the reading position and, at the tail, took the shell's bottom pin off for the rest of the session. The position was sampled only on the renderer's frame event, which fires after the paint, so an absolute jump — a scrollbar track click or a thumb drag, which can land anywhere in the transcript — painted one spacer-only gap first. The scroll bar emits a public change event synchronously before that paint; subscribing to it covers every scroll path. Also: the host-less session preview now follows the height prop across a resize instead of seeding its viewport rows once at mount; the item failure log carries the component stack and the error stack, matching ink; the two windowing options no caller ever set are gone, so the cap and the overscan are pinned by the tests that exercise them; and the cap's binding condition is stated correctly in the code and in both design docs. Adds a scroll-host harness so the frame-driven half is reachable from jsdom at all, and five tests over it, each mutation-proved against the defect it pins. * docs(cli): record the two windowing follow-ups the review named The bundle-level jsxDEV escalation Decision 8 compensates for at the item is still live for every other subtree, and a window-only move re-executes every mounted item's render path. Both are named in the design rather than left to be rediscovered. * fix(cli): measure transcript items by identity and settle pulled-in rows Each mounted item now registers its own node under `(kind, id)` instead of being recovered from its position among the transcript root's children, so the tool and task cards one subagent call mints no longer overwrite a single height slot, and the item key no longer collides as a React key either. The rows recorded are Yoga's computed height rather than the reported one, which is clamped to a row and would permanently charge a row to an item that paints nothing. An upward window move pulls in items whose spacer rows were charged at the estimate, so the frame that first measures them now subtracts what they turned out to cost from the scroll position — guarded to fire only strictly above the tail, where the reader's own scrolling has already released the shell's bottom pin. Measuring an item that was already mounted still owes nothing. Measuring also runs when no scroll host is found, so the session picker's preview records real heights too. * docs(cli): state the measured mutant victims in the windowing design doc * fix(cli): settle a transcript row whose recorded height went stale off-window The settlement for a row the window pulls in above the viewport only fired for an identity the height table had never seen. A row measured earlier, evicted, and then re-measured at a different height moved the offsets under a scroll position nothing re-anchored, so the page lurched on every wheel tick after a terminal resize or ctrl+O. Settle on the charge rather than on first-ever measurement, spend the charge on the measurement that answers it, and never charge an identity the previous commit already had mounted — which is what a `task-end` splice above the window would otherwise do, since it shifts the index space the charging loop walks while the identities on screen stay put. * docs(cli): keep the windowing design doc true about the stale-height settlement Decision 5 said the frame that *first* measures a pulled-in item settles it, and that the charge is always the estimate; the settlement now spends a charge on the measurement that answers it and never charges an already-mounted identity. The mutation ledger named ten tests on the scroll-host harness and a suite total that both moved. --------- Co-authored-by: Shaojin Wen <[email protected]> |
||
|
|
72316e6608 |
feat(web-shell): Reuse standard UI for Managed Agents (#13973)
* feat(web-shell): Reuse standard UI for Managed Agents Reuse native chat, model and Workspace controls for Java Managed Agent sessions while preserving admission retries and scoped drafts. Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): Clear accepted drafts after editor remount Keep accepted input ownership across session switches and response-loss retries. Snapshot original editor text in the local pending record so a reload can clear matching owner storage without an in-memory callback. Preserve original commits, foreign drafts and newer different drafts. Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): Clarify abandoned first-input admission Warn that first input may already be running and a new request can repeat its effects. Refresh the owning Session list after local abandonment and show uncertain recovery controls only once the active request settles. Add the focused review regressions for composer input and standalone UI. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: Shaojin Wen <[email protected]> |
||
|
|
55dcb50b35 |
feat(managed-agent): give a Monitor auto-stop its remote exit evidence (#13896) (#13929)
* feat(runtime-broker): split Runtime Session release into routine and drain arms (#13856) The H3 enablement's first design call: a Runtime Session release refuses 409 runtime_session_held while a background run is provably live, and the run holds its Runtime until its exit is proven — the turn-settle drain that SIGTERM'd live producers is gone from the routine arm. The drain arm (the Session close, and the control plane's close cascade through releaseSavedSession) keeps the status → terminate → status sweep, and the public :release route gains the optional "drain" flag that names it. The Monitor watch joins the hold: is_monitor payloads admit their own ledger row (background_v3_monitor), the sweep and the re-acquire scan ride the worker's pre-built monitor-status/monitor-stop maintenance route through a new ManagedMonitorProtocol (the mirror of ManagedShellProtocol), and the cancel guard covers both hold families. Design: docs/design/2026-10-11-managed-h3-enablement.md (+ zh-CN). Co-authored-by: Qwen-Coder <[email protected]> * feat(cli): hold the Runtime across turns and project platform stops (#13856) The harness side of the H3 hold: a turn whose routine Runtime Session release is refused 409 runtime_session_held settles clean — the designed hold, not a wedge — and the Session records the owed drain. The Session close drain-releases every held Runtime ahead of the loop stops and the publisher close, and a drain that cannot prove a stop fails the close honestly (503, reopenable) with monitors parked runtime_lost, the same unproven shape as a blocked settlement. The recovered-lease handback at close drains too; the cancellation path's fire-and-forget handback keeps routine semantics (a cancel route never stops a live run). A8: a platform-driven stop now projects as cancelled/stop_requested instead of a bare exited. The worker registry marks any stop it was asked to make (terminate/stopSession/stopAll), the marker rides the capture finalize's process evidence (present only when set, so the foreground lane's closed key set never sees it), and the publisher settles the record through requestStop + settleStopRequested; the monitor lane answers the same marker through the observation loop's onExit. An unproven end still settles failed, marked or not — the marker never mints a stop nobody proved. Witnesses: the publisher background suite settles stop_requested with the marker and exited without; the monitor loop suite covers the platform-stop arm; the hosted close-drain test watches the turn hold and the close's drain release; the turn suite pins the held/busy classification split. Co-authored-by: Qwen-Coder <[email protected]> * feat(core): enable the H3 monitor_run domain and the shell child_run kind (#13856) The gate lift itself, after the Runtime hold landed and the physical acceptance pass ran: monitor_run joins MANAGED_SESSION_ENABLED_DOMAINS and shell joins MANAGED_SESSION_ENABLED_CHILD_RUN_KINDS beside child_agent. The turn's background admission reads the kind gate now — the child_run domain never joins the plain list, so the old plain-domain read was a latent refusal the lift would otherwise have shipped. The pinned-disabled suites flip deliberately, following #13846's practice: the pin tests assert the enabled lists, the authority and publisher suites commit through the real gate, and a test that needs a gate shut closes it per test. The H3 design's status and the §13 hold text record the lift and the runtime_session_held coding. Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> * fix(core): close over a result manifest's page envelopes at commit (#13856) The staged HTTP session store persists a staged resource only when a commit's resource closure names it, and the walk that builds the closure never descended into a result manifest — so the page envelopes the manifest names stayed staged in memory and never persisted, and the Artifact read chain 404'd at the page hop. The #13856 rig pass's B2 arm proved it physically: the producer's bytes landed (the content rows held them, digest-true), the manifest sealed complete, and the page envelope it named was in no table. The walker now collects the page references a manifest names. Pages themselves name only segment digests (never refs), and segments persist through the tool-result publish route directly, so the chain closes: record → manifest → pages → segments. Witness: a manifest naming a page ref closes over exactly it; removing the branch turns the test red. The B2 rig arm re-runs green over it. Co-authored-by: Qwen-Coder <[email protected]> * docs(managed-agent): record the H3 enablement's physical acceptance (#13856) Co-authored-by: Qwen-Coder <[email protected]> * test(core): drop a stray enablement flag from the shell acceptance case (#13856) The case commits a `child_run` of the shell kind, which the production gate now admits, so the switch it flipped was never this suite's to flip — and `npm run typecheck` refused the file over the unknown property. * test(cli): run the H3 gates at their production default (#13856) Both suites mocked the monitor_run domain and the shell kind shut and asked each case to open them, so a case that never thought about the gate exercised a refusal production no longer produces. The flags now start open and a case that needs a gate shut closes it per test, and the kind mock sends every kind the production list carries through the real gate; only a kind production has not enabled — a workflow planting — stays a fixture, admitted so its case runs what it is about. * fix(cli): drain the Hook-owned Runtime at Session close (#13856) A Hook-configured Shell turn shares the Hook broker, and its `finish()` skips the release, so a background run that outlives the turn holds that Runtime and never reaches the Session's own held-Runtime drain: the close died on the Hook session's routine release refusing `runtime_session_held`, answering 503 forever and leaving the run alive. The close is the drain arm — an acquire still hands an earlier owner back without stopping anything, because the Session lives on. Co-Authored-By: Qwen Code <[email protected]> * fix(cli): park a Monitor auto-stop that cannot prove its stop (#13856) The quota and the idle window run in the Harness while the watch runs in the worker, and the client control route admits MCP and Hook operations only — no Harness-side `monitor-stop`. A remote handle's terminate therefore stopped nothing, yet the loop settled the record at `max_events`/`idle_timeout`: the task read as ended while the watch kept running and kept its Runtime held. A handle now reports whether its stop is proven, the supervised watcher answers from the supervisor's evidence, and an unproven auto-stop parks the record on the runtime_lost line the `stop()` call already uses. Giving an auto-stop its own remote exit evidence is recorded as future work in both languages. Co-Authored-By: Qwen Code <[email protected]> * fix(cli): let a parked Monitor consume the exit evidence that follows (#13856) The park ended the loop's observing and its record in one step, so the watch's own later end had nowhere to land: `onExit` and `stop` both returned early over `ended`, and the record stayed `recovery_blocked` with a null stopReason even after the worker exited, its hold released and its capture published complete. Observing and the terminal settle are separate flags now, so a park keeps the record open for the evidence that follows — a natural exit, a platform stop, or the close's proven drain. A terminal settle also clears the recovery reason a park or a rebuild left on the run: the record contract admits no reason for `settled` or `cancelled` and only a quota reason for `failed`, so carrying `runtime_lost` into them refused the very commit that proved the end. Co-Authored-By: Qwen Code <[email protected]> * fix(cli): keep a failed close's Monitor park open for the evidence (#13856) `stop()` marked the record terminal before it knew which arm it would take, so the `runtime_lost` park closed the very record it left unsettled. That arm is what a held-Runtime drain failure asks for: the close answers 503 and keeps the Session and its publisher alive for a retry, and neither the retry's proven drain nor the watch's own later end could reach the record — it stayed `recovery_blocked` with a null stopReason over a worker that had exited, a hold that had released and a capture that had published complete. Only the arm that actually settles now marks it terminal. Co-Authored-By: Qwen Code <[email protected]> * feat(managed-agent): give a Monitor auto-stop its remote exit evidence (#13896) A Monitor's quota and idle window run in the Harness while its watch runs in the worker, and the client control route admitted MCP and Hook operations only, so the remote handle's terminate stopped nothing and an auto-stop could only park its record (the #13889 interim). The Broker now admits Monitor operations on the client control route as recovery kinds, and only for one of the Runtime Session's own watches: the operation names the execution that started the watch and the Runtime call the worker derived its unit from, both checked against the watch's hold row in the ledger; anything else is refused before the worker sees it. A worker answer of `exited` settles that hold row there, as the release sweep would. These controls run outside the control fence, as the re-acquire scan's status ask does: the hold row already keeps the Runtime Session from releasing while the watch may run, and a fenced auto-stop still waiting on the worker would turn its own turn's routine release into runtime_session_busy and block the Session. The CLI gains `monitorControl()` with the watch vocabulary (running/exited/unknown, `exited` only with evidence), and the remote handle's terminate issues `monitor-stop` and proves the stop only on `exited`; any other answer, a refusal or a failure keeps the parked line. The worker answers only after the capture's finalize has run, and that finalize hands the exit to the loop while the auto-stop still waits: the exit is the stop's own evidence, so the loop settles `max_events` or `idle_timeout` there, after the delivered final output, and the worker's answer finds the record settled. A quota reached by the final window of an exit settles from that exit with no stop: the finalize is the proof, and a stop issued from inside it would wait on that same finalize. A stop now waits on a round trip, so the loop runs one at a time: only a loop that still observes starts an auto-stop, the close's stop waits out an auto-stop in flight and acts only on its park, and a stop keeps the settlement its own exit made before its answer returned. Since #13889 leaves a parked record open to the evidence that follows, the finalize's stop marker would otherwise settle the record ahead of the auto-stop, and the auto-stop's own settle would then conflict. Worker-side quota and idle bounds are not added: the record's quota counts the Harness's debounced windows, which the worker cannot reproduce, and a hold whose Harness never returns is a family-agnostic problem recorded as follow-up work. * fix(managed-agent): pin the Monitor control review gaps (#13896) A Monitor operationId that no hold row can carry — one with a NUL, or one too long once the row suffix is added — reached the ledger lookup, whose id bound threw and answered `runtime_broker_invalid_request` instead of the protocol-shaped `runtime_control_operation_invalid` every other refused Monitor operation gets. The Broker now screens the row id the same way the ledger admitted every row and refuses such an id as naming no watch (R1-4). Tests pin the rest of the review: an `unknown` stop answer keeps the hold row (R1-5); `monitorControl()` admits `running` and `unknown` as watch answers (R1-2); an end without evidence while a stop waits settles `watch_failed`, never the stop's reason (R1-1); and the resume case calls `resumeMonitorWatch` with its Runtime call, as production does (R1-3). * fix(managed-agent): keep a proven Monitor stop open until its final output lands (#13896) A proven auto-stop whose final capture the publisher refused still answers `exited`: the worker's completion records the delivery failure and keeps the evidence. The loop then settled the record terminal ahead of the sealed manifest, and the publisher's redrive of that finalize was refused by the successor rule, stranding the output at `pending`. The loop now keeps such a record open at the stop's reason, with no park and no terminal settle. The redrive delivers the exit, which settles at that reason after the sealed manifest lands. If it never lands, the Session close settles at the stop's reason with no second stop. The loop's fake executor now answers a proven stop only after delivering the exit, as the worker does; the two close-park cases model their failed drain as unproven. New cases: a proven stop with no exit stays open and unparked, and the real-publisher auto-stop E2E runs with a final forward the store refuses twice. Broker: pin the completion half of the control fence. A completed Monitor control must not end a fence it never began, or a fenced control still in flight no longer keeps the release busy. --------- Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: Qwen Code <[email protected]> |
||
|
|
bc682d1754 |
fix(cli): give bare ↑/↓ back to input history in Virtualized History (#14016)
#12518 routed a bare ↑/↓ on an empty composer to transcript scrolling in VP mode, on the theory that terminals without SGR mouse reporting deliver the wheel as arrow-key sequences. The composer is empty at rest, so the fallback fired on every deliberate ↑/↓: input history became unreachable from the arrow keys and only Ctrl+P / Ctrl+N still recalled it. That also contradicted the contract the project had already settled on — #4942 ("scroll with wheel or Shift+↑/↓ anytime, type messages anytime"), #5159 (closed with a docs workaround rather than by reassigning the keys), ui.useTerminalBuffer's own description, and both keyboard-shortcuts.md and troubleshooting.md, none of which #12518 updated. Gate the fallback behind a new opt-in ui.arrowKeysScrollTranscript (default false). Bare ↑/↓ walk input history again everywhere by default; a terminal that genuinely cannot report the wheel turns the setting on and gets #12518's behaviour verbatim, with Ctrl+P / Ctrl+N for history. The opt-in keeps every guard #12518 added — empty composer, transcript actually overflowing, ↓ still yielding to descendFromComposer — and non-VP mode is untouched. Docs now name the setting as the alternative for the tmux trackpad case. Fixes #14015 Co-authored-by: chiga0 <[email protected]> |
||
|
|
0df41f4464 |
test(managed-agent): clean up recovery fixtures after probe failures (#14018)
* fix(ci): recover reused runner checkouts and Java lock waits Co-authored-by: Qwen-Coder <[email protected]> * test(managed-agent): clean up recovery fixtures after probe failures * fix(ci): refresh Git metadata across Linux validation lanes Co-authored-by: Qwen-Coder <[email protected]> * fix(ci): refresh checkout metadata for triage jobs Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
c5098a2733 |
feat(managed-agent): H4 workflow child runtime (#13858)
* feat(managed-agent): H4 workflow child runtime (#13803)
Issue #13803's four exit checks land in one change because the
`managed-child_run` kind gate fires on every revision commitment: the
relay and cascade verbs on a workflow row would answer 409 until the
gate admits the kind, so the query widening (K2) and the gate flip
(K4) cannot be separated.
K1 — the ChildResultRelay discovery now selects `workflow` rows, and
creation branches on the record kind: a workflow launch mints exactly
one child Session under the parent's Workspace binding and lineage,
whose first Turn carries a `workflow_launch` block (pinned definition,
script, args) instead of a prompt. K3 — every produced launch pins
its definition from revision 1: the pin's digest is the sha256 of the
exact script bytes (revision 1 by convention, id `workflow/<name>`),
and the child's first Turn re-verifies the digest before spending
anything.
The daemon gains a `HOSTED_WORKFLOW_TOOL` declaration that mirrors
the Agent tool's admission behind its own kind reader; a workflow
child Session runs its first Turn deterministically in-process via
WorkflowRunner (its agent() calls are in-process AgentHeadless
subagents under ordinary caps, never nested managed grandchildren).
Wake, close, and consumption predicates treat both child Session
kinds through one funnel; quotas already count the two kinds
together.
One cross-language contract consequence: a workflow launch block is
the first prompt ever to carry fractional `args` across the daemon's
`sha256(JSON.stringify(prompt))` admission digest, so the SDK's JSON
digest substrate is canonicalized to ECMAScript byte-fidelity —
otherwise a fractional arg wedged the first Turn with a
deterministic `invalid_hosted_prompt`.
Physical acceptance in #13532's shape ran against the packaged stack:
end-to-end foreground and background workflow children with result
delivery verified on a real host; workspace-close cancellation is
blocked-with-reason on macOS (host capability, re-confirmed on the
final tree); a tampered pin refuses execution; the mid-interruption
re-claim path is partially observed and filed as issue #13838.
* fix(cli): refuse nested saved-workflow resolution outside the pinned slice (#13803)
The managed workflow child pins its script from the launch, but a
model-authored script's own workflow() call resolved saved workflows
the pin never examined, including bytes from the daemon host's own
~/.qwen/workflows (user tier). Admission now refuses any script whose
masked source invokes the injected workflow global, with the same
model-correctable text that names name, scriptPath, resumeFromRunId
and sourceRef as the deferred reference scope. The hosted runner
passage also passes restrictNestedScriptPaths as a second line, and
the declaration no longer advertises workflow() in this profile.
Witnesses: both workflow('x') and workflow({scriptPath}) forms refuse
without committing a child_run, the same text inside strings or
comments admits, and removing the admission gate turns both refusals
red.
* fix(sdk-java): render big numbers through the double the script peer holds (#13803)
The canonical writer computed its 2^53 exact-integer guard on a
value already narrowed through longValue(): Long.MIN_VALUE stayed
negative under Math.abs, and any BigInteger at or past 2^63 was
truncated -- sometimes sign-flipped -- before the check, printing
digits JSON.stringify never produces while both sides digest the
same corrupted text and therefore agree. Long and BigInteger now
flow through the shared double path, and the BigDecimal arm renders
through the double the parser could hold except in the subnormal
band, where the exact digits are the ECMAScript short form ('5e-324')
that a blunt double round-trip would print as '4.9e-324'.
Golden vectors pin Long.MIN_VALUE, 2^63, 2^64 and the decimal
examples (12345678901234567890.5 prints 12345678901234567000;
1E-400 prints 0; 1E+400 is refused); a 38-case parity probe matches
Node's JSON.stringify byte-for-byte, and removing each guard turns
the named witnesses red.
* fix(cli): close the review's pin-, admission-, preview- and doc-record gaps across the workflow child lane (#13803)
* fix(cli): adopt main's mount-refusal taxonomy in the workflow child admission path (#13803)
* fix(integration-tests): teach the hosted workspace tool-turn fixture the H4c workflow advertisement (#13803)
* fix(cli): harden the workflow child runtime lane against the review's round-1 findings (#13803)
Real-stack verification round 1 found the lexical admission's nested
workflow scan bypassable by indirection, and the foreground workflow
wait lost to any Harness restart. This change closes both:
- WorkflowRunner gains refuseNestedWorkflows: when set, every nested
workflow() resolution — by name or by path, aliased or through the
global — is refused, since any of them resolves bytes the launch pin
never examined. The managed workflow Turn passes it; the lexical
pre-launch scan stays as the model-friendly early refusal.
- acceptChildWorkflow's foreground arm parks #13708's durable
commitAwaitAgent checkpoint before it waits, exactly as the child
agent arm does, so a restart re-enters the wait from the journal
instead of declining the parent's Turn unresolved_after_settle.
Merge-integration cleanups ride along: the member-chain walk narrows
by kind where the roster view needs ChildAgentRun, and the recovery
gap fill and the shared ended text name the run's kind honestly.
Witnesses: indirection forms now settle failed with the named refusal;
the park commit is asserted with the exact checkpoint evidence; the
canonical-args refusal of the existing pre-commit gate is pinned
end to end (top-level and nested subnormals, nothing committed).
Co-authored-by: Qwen-Coder <[email protected]>
* fix(cli): settle a registry-cancelled workflow start as cancelled, not failed (#13803)
The real stack showed a WorkflowStartCancelledError — cancelStarting or
abortAll reaching the run's controller while the Turn's own signal stays
live — settling the child as child_failed because only the abort signal
classified the outcome. The turn now reclassifies that cause to
cancelled/cancelled, the same reading the Workflow tool gives for the
identical error; deadline_exceeded keeps its name, and error keeps its
name — only the generic error outcome yields to the cause.
Witness: the harness session settles with a cancelled turn_result when
the workflow start is torn down registry-side.
Co-authored-by: Qwen-Coder <[email protected]>
* fix(sdk-java): normalize lone surrogates before the digest and the insert, and keep protocol messages' field paths (#13803)
Two digest-honesty findings from the real stack and the reviews:
- The canonical encoder escapes an unpaired surrogate in the admission
digest, while no store column keeps the character; re-encoding the
read-back input then never matches the payloadDigest, and the Turn
retries forever (payloadDigest does not match promptContent). Input
admission now trades unpaired surrogates for U+FFFD before the payload
digest, the replay digest and the insert, on the API path and on the
child-creation path, so all three describe one representation.
- Four DaemonProtocolException messages that named only the context lost
their field path in this PR's digest work; they name the field again,
pinned by a witness that reads all four texts.
Witnesses: the child-creation and user-text admissions normalize before
the captured digest and insert, with valid pairs and non-strings left
untouched; the helper-rejection tests pass unchanged.
Co-authored-by: Qwen-Coder <[email protected]>
* docs(design): retire the stale closed-gate claims and record the runtime slice's settled honesty contract (#13803)
The H4c design's items 5 and 6 still named the kind gated and the Java
paths child-agent-only; the kind doc now names them closed only in its
own slice and opened with this one, in both languages. The runtime
design gains the behaviors the review rounds settled: the runner's
refuseNestedWorkflows line behind the lexical scan, the waiter arm's
durable park, the registry-side cancel settling cancelled through
WorkflowStartCancelledError, and lone-surrogate normalization at
input admission keeping the digest and the column one representation.
Co-authored-by: Qwen-Coder <[email protected]>
* fix(sdk-java): satisfy Checkstyle NeedBraces on the surrogate-normalization helpers (#13803)
The helpers added in
|
||
|
|
ef38ad7753 |
feat(web-shell): add read-only Excel artifact previews (#13664)
* feat(web-shell): add read-only Excel artifact previews * fix(vscode): refresh notices for Excel preview dependencies * fix(web-shell): bound Excel merge loading and preserve preview content * fix(web-shell): bound Excel preview work and preserve recovery * fix(web-shell): harden Excel preview values and recovery * fix(web-shell): include bundled ExcelJS dependency licenses * fix(web-shell): guard column expansion and complete ExcelJS notices * fix(web-shell): complete Excel color fallback and review guards * test(web-shell): follow artifact filename labels in Excel previews --------- Co-authored-by: 钉萁 <[email protected]> Co-authored-by: qwen-code-dev-bot <[email protected]> |
||
|
|
008c0de746 |
docs(sdk): clarify empty core-tool allowlists across SDKs (#13949)
Co-authored-by: Zhangchenhao-111 <[email protected]> |
||
|
|
7e5131d97b |
feat(runtime): add private CSI file worker with local-process isolation (#13899)
* feat(runtime): add private CSI file worker and retained history * feat(runtime): pair CSI file worker with local-process isolation * codex: address PR review feedback (#13899) * codex: address PR review feedback (#13899) * codex: address PR review feedback (#13899) * codex: address PR review feedback (#13899) |
||
|
|
f70344345d |
feat(managed-agent): recover Hosted G3 parked turns (#13777)
* feat(managed-agent): recover Hosted G3 parked turns Co-authored-by: Qwen-Coder <[email protected]> * fix(serve): preserve G3 recovery across channel integration Co-authored-by: Qwen-Coder <[email protected]> * test(managed-agent): align recovery fixtures and acceptance evidence Co-authored-by: Qwen-Coder <[email protected]> * test(managed-agent): align hosted process fixtures with recovery storage v2 The fake hosted session store now advertises supportedStorageVersion 2 and accepts the commit-v2 route like the Java store, and the packaged no-tool stream pin follows the durable delta projection (session_update chunk, managed_journal_event record, turn_complete). The e2e runner pin accepts the Linux-gated durable-local-process value. Co-authored-by: Qwen-Coder <[email protected]> * fix(hosted): address recovery review feedback (#13777) * fix(hosted): restore cleanup fence and update process gates (#13777) * fix(serve): bound native approval recovery plans Co-authored-by: Qwen-Coder <[email protected]> * fix(serve): keep approval fallback consistent across recovery Co-authored-by: Qwen-Coder <[email protected]> * fix(managed-agent): preserve G3 recovery intent and cleanup liveness Co-authored-by: Qwen-Coder <[email protected]> * fix(test): defer G3 gate success output until teardown Keep both G3 success records behind the final failure and signal checks, preserving their JSON payloads and successful output order. Extend the exit guard regression to cover both deferred records. Co-authored-by: Qwen-Coder <[email protected]> * test(managed-agent): align G3 cleanup gate fixtures Co-authored-by: Qwen-Coder <[email protected]> * fix(managed-agent): align Java envelope kinds with G3 Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
9f14e7d9bb |
feat(managed-agent): verify tool publications asynchronously (#13654)
* feat(managed-agent): verify tool publications asynchronously * codex: fix CI failure on PR #13654 * codex: address PR review feedback (#13654) * codex: address PR review feedback (#13654) * codex: address PR review feedback (#13654) * codex: address PR review feedback (#13654) Preserve the current main actor-key migration and append async verification at V57. Cover both the legacy-to-main and main-to-async upgrade histories. * codex: address PR review feedback (#13654) * fix(managed-agent): recover lapsed activations and failed finishes A renewable activation horizon was recorded as a permanent failure, and a permanently failed finish left the publication stuck in FINISHING. Give the MariaDB job enough time for the new async integration tests. * fix(managed-agent): verify accepted CSI claims after retirement Async verification used PRODUCE, so a settled CSI execution with a retirement row permanently failed work that the claim path would finish. * fix(managed-agent): defer expired grants and drop failed terminal bytes A real activation lapse expires the publication grant on the same clock, so the grant check failed the operation before the activation deferral. A failed finish also refunded its charge while leaving a non-VERIFIED object that blocked collection. * fix(managed-agent): keep object deletes and scan errors off the failure transaction A failed finish deleted its object inside the database transaction, so a rollback could drop the bytes without recording FAILED. A handler exception also aborted the rest of the verification page and retried the same row. * fix(managed-agent): preserve transient retries and writer fences Co-authored-by: Qwen-Coder <[email protected]> * fix(managed-agent): distinguish verification corruption from authority failures Keep accepted verification pending during session recovery and classify corruption only from verified byte mismatches. Preserve permanent writer fences and retry unclassified reads through complete capture validation. Co-authored-by: Qwen-Coder <[email protected]> * fix(managed-agent): fail verification on recovery blocks Restore permanent failure for accepted asynchronous work when Session recovery is blocked. Exercise the production recovery-block entry point without resetting the Session to READY, and document the fencing policy in both languages. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
b675c97461 |
feat(web-shell): add Managed child Agent task panel (#13936)
* feat(web-shell): add Managed child Agent task panel Co-authored-by: Qwen-Coder <[email protected]> * docs(web-shell): record child task panel acceptance evidence Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): keep task panel copy out of transcript exports Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): synchronize task cancellation settlement Publish validated task snapshots before clearing cancellation recovery, keep known terminal state against delayed reads, and clear obsolete admission contention once the task ends. Keep background task polling quiet and align detail timestamps and labels with the panel language. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
e9789d0911 |
feat(mods): Add tool interception and permission checks (PR3a) (#13950)
* feat(mods): Add tool interception and permission checks * fix(mods): Preserve execution facts across result references --------- Co-authored-by: qqqys <[email protected]> |
||
|
|
364593cb25 |
feat(web-shell): add action reminders and in-panel notification access (#13832)
* feat(web-shell): notify for approvals and user questions * fix(web-shell): isolate notification help and authorization attempts * fix(web-shell): preserve notification authorization and keyboard focus --------- Co-authored-by: 钉萁 <[email protected]> |
||
|
|
f3f5e005a3 |
feat(web-shell): support copying selected reply content (#13833)
* feat(web-shell): support copying selected reply content * fix(web-shell): preserve selected reply data and menu lifecycle * fix(web-shell): keep selection copying faithful to visible content --------- Co-authored-by: 钉萁 <[email protected]> |
||
|
|
e9b87acd73 |
fix(web-shell): localize and dismiss voice feedback (#13938)
* fix(web-shell): localize and dismiss voice feedback * fix(web-shell): exclude dictation messages from transcript bundle --------- Co-authored-by: 钉萁 <[email protected]> |
||
|
|
b1b2b2c6df |
fix(managed-agent): config and API-surface hygiene from the #12692 R2 review (#13335)
* fix(managed-agent): config and API-surface hygiene from the #12692 R2 review
Nine R2 review follow-ups on #12692, re-verified against current main
(
|
||
|
|
c528b37e83 |
fix(managed-agent): settle provably never-started dispatches at session open (#13801) (#13917)
* fix(managed-agent): settle provably never-started dispatches at session open (#13801) * fix(managed-agent): probe never-started dispatches without reacquiring a released runtime session (#13801) * fix(managed-agent): own the :process ledger key in the Broker client and pin review witness gaps (#13801) Co-authored-by: Qwen-Coder <[email protected]> * fix(managed-agent): attribute never-started skip causes in diagnostics and pin Gap A scope (#13801) --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
1bbaf93e8d |
feat(managed-agent): enable the H3 Monitor and background Shell gates (#13856) (#13889)
* feat(runtime-broker): split Runtime Session release into routine and drain arms (#13856) The H3 enablement's first design call: a Runtime Session release refuses 409 runtime_session_held while a background run is provably live, and the run holds its Runtime until its exit is proven — the turn-settle drain that SIGTERM'd live producers is gone from the routine arm. The drain arm (the Session close, and the control plane's close cascade through releaseSavedSession) keeps the status → terminate → status sweep, and the public :release route gains the optional "drain" flag that names it. The Monitor watch joins the hold: is_monitor payloads admit their own ledger row (background_v3_monitor), the sweep and the re-acquire scan ride the worker's pre-built monitor-status/monitor-stop maintenance route through a new ManagedMonitorProtocol (the mirror of ManagedShellProtocol), and the cancel guard covers both hold families. Design: docs/design/2026-10-11-managed-h3-enablement.md (+ zh-CN). Co-authored-by: Qwen-Coder <[email protected]> * feat(cli): hold the Runtime across turns and project platform stops (#13856) The harness side of the H3 hold: a turn whose routine Runtime Session release is refused 409 runtime_session_held settles clean — the designed hold, not a wedge — and the Session records the owed drain. The Session close drain-releases every held Runtime ahead of the loop stops and the publisher close, and a drain that cannot prove a stop fails the close honestly (503, reopenable) with monitors parked runtime_lost, the same unproven shape as a blocked settlement. The recovered-lease handback at close drains too; the cancellation path's fire-and-forget handback keeps routine semantics (a cancel route never stops a live run). A8: a platform-driven stop now projects as cancelled/stop_requested instead of a bare exited. The worker registry marks any stop it was asked to make (terminate/stopSession/stopAll), the marker rides the capture finalize's process evidence (present only when set, so the foreground lane's closed key set never sees it), and the publisher settles the record through requestStop + settleStopRequested; the monitor lane answers the same marker through the observation loop's onExit. An unproven end still settles failed, marked or not — the marker never mints a stop nobody proved. Witnesses: the publisher background suite settles stop_requested with the marker and exited without; the monitor loop suite covers the platform-stop arm; the hosted close-drain test watches the turn hold and the close's drain release; the turn suite pins the held/busy classification split. Co-authored-by: Qwen-Coder <[email protected]> * feat(core): enable the H3 monitor_run domain and the shell child_run kind (#13856) The gate lift itself, after the Runtime hold landed and the physical acceptance pass ran: monitor_run joins MANAGED_SESSION_ENABLED_DOMAINS and shell joins MANAGED_SESSION_ENABLED_CHILD_RUN_KINDS beside child_agent. The turn's background admission reads the kind gate now — the child_run domain never joins the plain list, so the old plain-domain read was a latent refusal the lift would otherwise have shipped. The pinned-disabled suites flip deliberately, following #13846's practice: the pin tests assert the enabled lists, the authority and publisher suites commit through the real gate, and a test that needs a gate shut closes it per test. The H3 design's status and the §13 hold text record the lift and the runtime_session_held coding. Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> * fix(core): close over a result manifest's page envelopes at commit (#13856) The staged HTTP session store persists a staged resource only when a commit's resource closure names it, and the walk that builds the closure never descended into a result manifest — so the page envelopes the manifest names stayed staged in memory and never persisted, and the Artifact read chain 404'd at the page hop. The #13856 rig pass's B2 arm proved it physically: the producer's bytes landed (the content rows held them, digest-true), the manifest sealed complete, and the page envelope it named was in no table. The walker now collects the page references a manifest names. Pages themselves name only segment digests (never refs), and segments persist through the tool-result publish route directly, so the chain closes: record → manifest → pages → segments. Witness: a manifest naming a page ref closes over exactly it; removing the branch turns the test red. The B2 rig arm re-runs green over it. Co-authored-by: Qwen-Coder <[email protected]> * docs(managed-agent): record the H3 enablement's physical acceptance (#13856) Co-authored-by: Qwen-Coder <[email protected]> * test(core): drop a stray enablement flag from the shell acceptance case (#13856) The case commits a `child_run` of the shell kind, which the production gate now admits, so the switch it flipped was never this suite's to flip — and `npm run typecheck` refused the file over the unknown property. * test(cli): run the H3 gates at their production default (#13856) Both suites mocked the monitor_run domain and the shell kind shut and asked each case to open them, so a case that never thought about the gate exercised a refusal production no longer produces. The flags now start open and a case that needs a gate shut closes it per test, and the kind mock sends every kind the production list carries through the real gate; only a kind production has not enabled — a workflow planting — stays a fixture, admitted so its case runs what it is about. * fix(cli): drain the Hook-owned Runtime at Session close (#13856) A Hook-configured Shell turn shares the Hook broker, and its `finish()` skips the release, so a background run that outlives the turn holds that Runtime and never reaches the Session's own held-Runtime drain: the close died on the Hook session's routine release refusing `runtime_session_held`, answering 503 forever and leaving the run alive. The close is the drain arm — an acquire still hands an earlier owner back without stopping anything, because the Session lives on. Co-Authored-By: Qwen Code <[email protected]> * fix(cli): park a Monitor auto-stop that cannot prove its stop (#13856) The quota and the idle window run in the Harness while the watch runs in the worker, and the client control route admits MCP and Hook operations only — no Harness-side `monitor-stop`. A remote handle's terminate therefore stopped nothing, yet the loop settled the record at `max_events`/`idle_timeout`: the task read as ended while the watch kept running and kept its Runtime held. A handle now reports whether its stop is proven, the supervised watcher answers from the supervisor's evidence, and an unproven auto-stop parks the record on the runtime_lost line the `stop()` call already uses. Giving an auto-stop its own remote exit evidence is recorded as future work in both languages. Co-Authored-By: Qwen Code <[email protected]> * fix(cli): let a parked Monitor consume the exit evidence that follows (#13856) The park ended the loop's observing and its record in one step, so the watch's own later end had nowhere to land: `onExit` and `stop` both returned early over `ended`, and the record stayed `recovery_blocked` with a null stopReason even after the worker exited, its hold released and its capture published complete. Observing and the terminal settle are separate flags now, so a park keeps the record open for the evidence that follows — a natural exit, a platform stop, or the close's proven drain. A terminal settle also clears the recovery reason a park or a rebuild left on the run: the record contract admits no reason for `settled` or `cancelled` and only a quota reason for `failed`, so carrying `runtime_lost` into them refused the very commit that proved the end. Co-Authored-By: Qwen Code <[email protected]> * fix(cli): keep a failed close's Monitor park open for the evidence (#13856) `stop()` marked the record terminal before it knew which arm it would take, so the `runtime_lost` park closed the very record it left unsettled. That arm is what a held-Runtime drain failure asks for: the close answers 503 and keeps the Session and its publisher alive for a retry, and neither the retry's proven drain nor the watch's own later end could reach the record — it stayed `recovery_blocked` with a null stopReason over a worker that had exited, a hold that had released and a capture that had published complete. Only the arm that actually settles now marks it terminal. Co-Authored-By: Qwen Code <[email protected]> * fix(cli): keep a parked Monitor resumable only when it is live (#13856) Two faults in the parked-monitor path the review round surfaced. A parked record has stopReason null exactly like a live watch, so the resume gate re-adopted it after a restart and its first post-quota observation was refused and swallowed inside the loop's queue; the gate now also rules out the recovery_blocked state (R1-55). And `onExit`'s final flush could cross maxEvents and park the record with a false runtime_lost revision ahead of the exit's own settle — a crash between the two commits would stick it — so the exit path's flush no longer fires the quota settle: the physical end supersedes the quota (R1-30). Co-Authored-By: Qwen Code <[email protected]> * fix(cli): tolerate an already-gone Runtime in the close drain (#13856) The close's held-Runtime drain treated every broker refusal as close-fatal, so a Session whose held Runtime was already gone (404 runtime_session_not_found) answered 503 on every retry and stayed half torn down — hooks and the wake pump closed, the publisher and the session entry never. A 404 names nothing left to stop, so the close now discharges it and proceeds; every other refusal is unproven and still parks honestly (R1-1). Co-Authored-By: Qwen Code <[email protected]> * fix(cli): settle a parked Monitor from its real exit evidence (#13856) The no-observer finalize branch re-committed a parked record's capture tail as an observation before settling it: the quota park refused it as an over-quota sequence and the idle park as an illegal successor revision, so a parked watch whose real end later arrived stayed `recovery_blocked` with a null stopReason even after the worker provably exited and released its hold. A parked record's observation period already ended, so the tail is not a new observation — the exit evidence settles the record directly (R1-55). Co-Authored-By: Qwen Code <[email protected]> * fix(cli): settle a crashed wake turn over a held Runtime (#13856) A wake turn that started a background run and then crashed left its Runtime held by design, so the crash aftermath's routine release refused `runtime_session_held` and the whole aftermath reported pending: the wake input never settled and the Session's close never learned it owed the drain. The aftermath now reads the refusal as the designed hold, registers the Runtime for the close's drain, and keeps settling the crashed input — without turning the routine recovery into a drain, which would stop a background run the user meant to keep (R1-28). Co-Authored-By: Qwen Code <[email protected]> * test(cli): hold the held-Runtime set in a local in the wake-crash case (#13856) The R1-28 case read `session.heldRuntimeTurns` off a session cast `as never`, which the package build's `tsc` refuses (TS2339) while `tsc --noEmit`'s typecheck tsconfig does not — so the failure only ever showed up as a red CI build lane. Hold the set in a local and assert on that. Co-Authored-By: Qwen Code <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: Qwen Code <[email protected]> |
||
|
|
9da6c6f7c9 |
docs(managed-agent): Plan the EventTransport MQ2 producer/consumer wiring in four sub-slices (#13939)
* docs(managed-agent): Plan the EventTransport MQ2 producer/consumer wiring in four sub-slices Phase plan for MQ2 (the parent design's producer/consumer wiring phase), decomposed into four independently reviewable sub-slices: MQ2a pins a Java mirror of the frozen envelope contract with a fixture consumer; MQ2b adds the authoritative-journal publication point and the LiteTopic producer behind a default-off flag; MQ2c wires the public-event lane and the wake/materialization consumers; MQ2d runs the fault-matrix E2E in CI. All MQ2 entry gates are closed (MQ1 #13909, §5.1 #13927 + staging, §11 #13933). Both languages in lockstep. Part of #12380 * docs(managed-agent): Fix the MQ2 plan's dependency gate and add the handoff acceptance Review follow-up on #13939: the rocketmq-client-java:5.1.0 client is a shaded artifact (io.grpc/com.google relocated to private namespaces), so the Boot BOM never re-aligns them and the 'Boot-aligned vs pinned gRPC' gate was built on a wrong premise — the gate is now empirical (clean dependency tree and packaging, plus a server start and a broker round-trip). And the owner-handoff recovery acceptance is added to MQ2c/MQ2d/Validation: during a handoff the stale subscriber's fenced no-op ACK advances the group offset, so the new owner recovers the pending item by SQL reconciliation, never by assuming MQ delivery. Both languages in lockstep. |
||
|
|
e17ad4caef |
fix(managed-agent): order publication placement before retention (#13894)
* fix(managed-agent): order publication placement before retention * codex: address PR review feedback (#13894) * codex: address PR review feedback (#13894) * codex: address PR review feedback (#13894) |
||
|
|
049c56d82a |
feat(mods): Add middleware dispatch and recovery (PR2b) (#13915)
* feat(mods): Add middleware dispatch and recovery (PR2b) * fix(mods): Correct log budgets and worker regression checks Keep charging caller execution while void log notifications are pending. Update the relocated worker test for the grouped protocol and cancellation buffer. Clarify and protect the Claude-compatible catch stopping and replay rules. * fix(mods): Bound idle work and preserve dispatch capacity --------- Co-authored-by: qqqys <[email protected]> |
||
|
|
23add5ef9d |
docs(managed-agent): Resolve the EventTransport §11 routing and wake-carrier questions (#13933)
* docs(managed-agent): Resolve the EventTransport §11 routing and wake-carrier questions Decided 2026-10-12 with the maintainer: the wake envelope keeps the committed wake.requested record as its carrier (already in the envelope v1 vocabulary; a distinct WakeIntent would store the same fact twice), and Session-envelope consumption uses a shared bootstrap lane plus owner-side dynamic subscription — the activation fence, not the broker, decides which node may act, so neither consumer-group-per-Session nor group-per-node fan-out is needed. Both languages in lockstep. Part of #12380 * docs(managed-agent): Sync §8/§9 with the §11 decisions Review follow-up on #13933: the §9 carrier bullet still said the wake-intent carrier was unpicked until #13300 lands — it now records the resolved decision (wake.requested, superseding #12827 question 3). And the routing decision's sole-subscriber claim is qualified as the steady state: during an owner handoff (lease expiry without a clean unsubscribe) a stale subscription can coexist, its fenced no-op ACK still advances the group offset, and the new owner can miss the notification — SQL reconciliation covers the miss, and the handoff case joins MQ2's verification list. Both languages in lockstep. |
||
|
|
4cd30565b2 |
docs(managed-agent): Resolve the EventTransport §5.1 LiteTopic verification ledger (#13927)
* docs(managed-agent): Resolve the EventTransport §5.1 LiteTopic verification ledger All five UNVERIFIED rows resolve in LiteTopic's favor from public documentation, dated 2026-10-12: at-least-once with retry + DLQ, per-LiteTopic FIFO, per-ClientID subscription, broker-persisted RocksDB offsets, gRPC SDK >= 5.1.0 / server >= 5.5.0; classic consume-side semantics do not carry over. §5.2 table cells updated, §11 question 3 closed (doc-level verification pinned in §5.1; the MQ2-start staging re-check remains). Both languages in lockstep. Part of #12380 · Refs #13200 * docs(managed-agent): Correct the dispatch semantics and pin sources in the §5.1 resolution Review follow-up on #13927: LiteTopic dispatch is shared within a consumer group — exactly one client per group receives each message (source-verified at the 5.5.0 tag: LiteEventDispatcher, and the (group, LiteTopic, 0)-scoped lock/FIFO/offset in PopLiteMessageProcessor), so cross-node fan-out needs one consumer group per node; the capability table, the resolution item and the §11 routing question now say so. Also: correct the Aliyun quota figures (23.2M on rmq.u2.1000xlarge, 1M on rmq.u2.30xlarge; 200 = consumption TPS; TTL frees the LiteTopic count, not the subscription count), mark the fixed-interval retry as commercial-doc-only evidence in the table, and make every resolution source clickable with version-pinned links. Both languages in lockstep. * docs(managed-agent): Scope the shared-dispatch claim to one dispatch attempt R2 review follow-up on #13927: a lost ACK or client inactivity re-dispatches the in-doubt message, possibly to a different group member, so 'exactly one client receives each message' overstated the source guarantee. The resolution block, capability table and §11 now say one dispatch attempt selects exactly one client, redelivery may move nodes, and there are no per-node copies — consumer idempotence and the activation fence absorb that. PR description synced to match. Both languages in lockstep. * docs(managed-agent): Sync §8/§9 with the §5.1 resolution yiliang114's review on #13927: two places still described the pre-resolution state. §9's risk bullet now records that the doc-level verification resolved the delivery/ordering items on 2026-10-12 and that the residual exposure is the MQ2-start staging re-check; the §8 consumer-crash row drops the 'must be verified in §5.1 first' note to 'verified at doc level, re-checked against staging at MQ2-start'. Both languages in lockstep. |
||
|
|
e977bdac71 |
fix(cli): bound managed function-hook module evaluation and keep hold-fenced owners recoverable (#13243)
* fix(cli): bound managed function-hook module evaluation and fix its test import PR #13129 merged with two unresolved real findings from its round-4 review: - A managed function handler module is evaluated with an unbounded await import(): it ignores both the manifest-mandated function timeout and the operation abort signal. A module whose top-level await never settles wedges the operation permanently, pin one of the 16 admission slots and makes runtime close() never resolve. Race the import against the manifest function timeout and the operation abort signal: timeout settles as managed_hook_handler_unavailable (same as a failed import), abort settles the operation as cancelled, matching the existing pre-dispatch abort outcome. - The 'ignore-abort' interruption test imported its handler module via a raw absolute filesystem path; on Windows import() throws ERR_UNSUPPORTED_ESM_URL_SCHEME there, failing the schedule-only test_windows lane on main, which never runs on pull requests. Import through pathToFileURL like the production sibling does. Add regression tests for both newly bounded paths, including one that fails close() hanging was verified by mutation: with the bounding removed, both new tests fail (8s status poll timeout; afterEach close hang), with it, the full file passes. * fix(cli): fence abandoned function-hook module evaluation as unknown and bound its budget Address PR #13243 review round 1: - R1-1: a module-evaluation timeout settled with the same managed_hook_handler_unavailable code a genuinely failed import produces, which the host records as not_started_proven — but ESM evaluation cannot be cancelled, so the abandoned module may already have run top-level effects. Report the timeout with a dedicated managed_hook_module_evaluation_timeout code instead, letting the host fence the execution honestly as outcome_unknown; keep the unavailable code for imports that actually rejected. - R1-2: the import budget reused the manifest's callback timeout verbatim, legally as small as 1 ms, turning cold-but-healthy evaluation into false handler-unavailable receipts. Give evaluation its own floor (500 ms) while keeping the callback budget unchanged. - R1-1/abort: hasHolds() now accounts for an abandoned evaluation besides view.state, so workspace release stays fenced while handler top-level code may still be running; a never-settling evaluation keeps the hold — the deliberate honest-hold trade-off, documented at the flag. - R1-3: pin the import rejection arm with a broken-module test whose receipt must arrive well before the 8s status poll. - R1-4: pin the manifest timeout as the evaluation bound with an elapsed-time assertion that reds on a 5s constant mutation. Hosted-side regression: a module-evaluation-timeout receipt fed through the broker records execution outcome_unknown, keeps one hook-execute request, and refuses retry; genuine handler_unavailable receipts still record not_started_proven. Each production change was mutation-verified: removing the floor, dropping the rejection arm, decoupling hasHolds from the flag, or replacing the budget with a 5s constant each reds a named test. * fix(cli): teach earlier-owner release to skip a hold-fenced owner and retry later Address PR #13243 review round 2: - R1-1 (critical): the abort arm publishes a terminal receipt while hasHolds() keeps reporting unfinished work (per round 1), so releaseEarlierOwners computed the earlier owner as settled, called release(), got a 409 from closeSessionAdmission, and rethrew — blocking every replacement activation's acquire on the same daemon until it dies. Tolerate the refusal like the already-tolerated 404: accept 409 with managed_runtime_identity_conflict or managed_runtime_provider_operation_failed, skip the owner without recording it released, and let a later acquire retry the release. The failed behavior is pinned by two new rows in the existing earlier-owner reconciliation matrix (the runtime_session_busy row keeps refusing, so only the hold-fenced pair is tolerated). - R1-4: add a discriminating case that proves the manifest timeout governs evaluation above the 500 ms floor (1500 ms evaluation under timeout 3000); reword the elapsed-latency comment which the floor had made an over-claim. - R2-1: pin the hold's self-healing half — an evaluation that overruns its budget and then completes must clear the hold (700 ms evaluation, timeout 10), so a `finished`-guard tidy-up cannot wedge workspace release for finished work. - R2-2: update the managed-hooks design doc in both languages: name the module-evaluation phase, its max(timeout, 500 ms) budget, the settled-at-Runtime admission release with retained hold, the outcome_unknown fencing code, and the hold-fenced earlier-owner release retry; correct the 16-slot sentence which previously claimed the pairing without exception. Each fix arm mutation-verified: removing the 409 tolerance reds exactly the two new matrix rows; substituting the bare floor for the Math.max reds the manifest-governs case; guarding the flag clear with `!finished` reds the over-budget hold-release case. * test(cli): widen evaluation-timing margins and sharpen the release-refusal docs Audit follow-ups on this branch, plus merging origin/main (which changed the same release path in #13195) so the branch is verified on the base it will land on: - The two timing-sensitive cases had thin margins against CI contention: the manifest-governs case declared timeout 3000 for a 1500 ms evaluation (2x) and the over-budget case a 700 ms evaluation against the 500 ms floor (1.4x). Widen to 5000 and 900 ms; both mutation witnesses still red exactly as before (bare floor at the budget site; `!finished` guard on the flag clear). - The design doc described the skipped release as hold-fencing only, while the code tolerates any 409 conflict on that path — which is also how a genuine identity conflict answers. State both refusals (absent 404, conflicting 409) and that the owner is left unreleased for a later acquire, in both language versions. Re-verified on the merged base: 964 passed / 4 Linux-only skipped across the seven related files with no unhandled-error output, build, typecheck, ESLint and Prettier all exit 0, and the hold-fenced tolerance mutation still reds exactly its two matrix rows while the refusal rows keep throwing. * fix(cli): name a hold-fenced owner release and log the refusals this PR absorbs Address PR #13243 review round 3 (all seven findings are Suggestions): - R3-3: close() releases the current owner without recognising a hold fence, so an abandoned module evaluation that outlived the cancelled receipt let a bare Broker 409 escape to the DELETE/detach route, which answered a generic managed_session_close_failed while the durable record said the execution settled cleanly. Classify that refusal as the Hook recovery-required condition and give the route catch an instanceof branch, so the 503 names the retained hold. The pairing follows the hosted_mcp_recovery_required 503 already in that route, and no consumer outside this file keys on the code. A refusal that is not a hold fence still propagates unchanged. - R3-4: a tolerated release refusal was dropped without a trace in a file that had no logger. Log both tolerated branches — the absent one books the owner released permanently, and one of the two fenced codes is the provider worker's catch-all, so an absorbed internal failure was indistinguishable from a hold fence. - R3-5: the `continue` that keeps a hold-fenced owner out of releasedOwners was the whole retry-later guarantee and nothing pinned it. Extend the reconciliation matrix with a second release pass on the same instance: the two 409 rows must retry, the 404 row must not. - R2-1 follow-up: the over-budget case now re-queries its receipt after the hold releases, like its five siblings, and its fixture callback writes a marker so the test also pins that a late-finishing evaluation never dispatches into a settled occurrence. - R3-1, R3-2, R2-2 follow-up: correct the design doc in both languages. The release sentence now names the exact codes, splits the 404 (booked released, never retried) from the 409 (skipped, retried) outcome, and states that any other refusal propagates; the handler-unavailable sentence no longer claims a proof for a module rejected after it evaluated in full; and the recovery section scopes the lifetime-hold claim to operations that never settle, matching the paragraph and test that pin the release. Mutations re-verified: removing the `continue` reds exactly the two 409 matrix rows; dropping the close() classification reds the new current-owner case while the non-fence case still leaks the raw refusal; returning a different receipt once the evaluation flag clears reds only the over-budget re-query; a probe fixture confirms the marker assertion observes a real dispatch, so its absence claim is not vacuous. * fix(cli): retry a hold-fenced Hook owner each turn and stop reporting DELETE as done Address PR #13243 review round 4 (one Critical, six Suggestions): - R4-1 (critical): absorbing the hold-fenced release left the promise of a later retry unimplemented. acquire() short-circuits once acquired, so no in-life pass ever re-attempted it, and close() absorbed the same fence and resolved — letting DELETE answer 204 and delete the Session together with the only state that could retry. The earlier owner then stayed attached at the Runtime for the worker's lifetime, which is what blocks tools across the Workspace, and the merge base's self-healing (refusal propagates, 503, Session retained, released once the hold clears) was lost. Track fenced owners, retry them on each turn's acquire, and make close() report the Hook recovery-required condition while any earlier owner is still fenced, so DELETE keeps the Session attached and retryable. The retry shares one pass across parallel Hooks and still consults releasedOwners, so an owner is never released twice, and acquire() keeps proceeding on a fence. - R3-4 follow-up: the absorbed refusals were logged through the debug logger, which writes nothing on a daemon started without --debug. Report them on the serve convention (stderr) instead, naming the owner and the refusal, and drop the logger this file did not previously have. - R4-2: the close-route translation to 503 hosted_hook_recovery_required was asserted nowhere, so removing it survived the suite. Pin body.code on a DELETE whose Hook owner release is fenced, and keep the existing case that pins the generic code for a non-Hook close failure. - R4-4: the retried assertion cleared the release spy before close(), and close() itself re-attempts the release, so it observed the close pass rather than the acquire-path retry it claimed to pin. Clear after close(). - R2-1 remainder: nothing pinned that an abandoned evaluation releases its admission slot. Fill the 16-slot budget with abandoned function evaluations, assert the holds are all retained, and assert the next operation is dispatched rather than answered with the quota refusal. - R4-3, R3-2 follow-up: scope the design doc in both languages — the close-time naming applies to a Hook Session that owns its Runtime owner, while a Session sharing the MCP owner is released by that session and still reports the generic code; and handler_unavailable covers a module rejected before, partway through, or after top-level execution, so the proof does not cover effects that already ran. Also record that the evaluation budget bounds only yielding evaluations, and fix the Chinese text that said a settle releases all 16 admission slots where the English says one of 16. Mutations verified: widening the admission filter to count held evaluations reds the new admission case; booking a fenced owner as released reds both 409 matrix rows and the in-life retry case; removing the route branch reds the new body.code assertion while the generic-code case stays green. * fix(cli): stop a stale owner's release refusal from failing a healthy Hook turn Address PR #13243 review round 5 (Critical only, per the post-round-five cutoff; the six Suggestions are recorded in #13133). R4-1 (critical, fix-induced): the per-turn retry added in the previous round made acquire() fallible on an already-acquired Session. retryFencedOwners() had no catch and releaseOwner() rethrows any refusal outside the tolerated set, so a stale earlier owner answering 409 runtime_session_busy, a 5xx, an unconfirmed release or a client timeout escaped acquire() before any hookControl was sent. The hook never dispatched although the Session's own owner was healthy, the child record stayed at execution 'intent' with a null resultRef, hasPendingOperations stayed true, and every later turn threw recovery-required; DELETE failed the same way through close(). The id also stayed in fencedOwners, so the failure repeated every turn, and the loop abandoned the remaining fenced owners on the first throw. Absorb and report each owner's retry failure inside the shared single-flight pass, leaving the owner fenced for the next turn. The catch sits in retryFencedOwners() rather than releaseOwner(), so the acquisition path still propagates an untolerated refusal (pinned by the matrix's allowed:false rows) and close() still leaks a non-fence refusal raw (pinned by its own case). releasedOwners remains the sole once-only guarantee and the retry pass still consults it. Also scope the design doc in both languages: the acquisition path propagates any other refusal and blocks the replacement activation, while a later turn's retry pass absorbs and reports it, because that pass runs on an already-acquired Session and must not fail a healthy live turn for a stale owner's sake. Pin: a sibling of the in-life retry case fences the earlier owner on the first acquire(), then answers a non-tolerated 409 runtime_session_busy on the retry, and asserts the turn still dispatches one hook-execute, that attempts reached two, that hasPendingOperations is false, that the refusal was reported naming the owner, and that a later turn retries again. Mutation-checked: removing the absorbing catch reds this case with the raw "Runtime Broker returned HTTP 409 (runtime_session_busy)" escaping into the live turn. * fix(cli): keep a hold-fenced close() from releasing the Session's own Hook owner * fix(cli): fence a Hook cancel landing mid module evaluation as outcome_unknown An abort that lands while a function handler's module is still evaluating published a definite cancelled receipt, so the Harness committed a known outcome while the module's top-level code was still running: the status route answered recoveryBlocked=false and drain() passed while hasHolds() kept refusing DELETE and detach with 503 for the worker's lifetime. Report the abandonment with a dedicated code instead, the way the evaluation timeout does, so the Harness fences the execution as outcome_unknown. Keep the settled cancelled receipt for a cancel that lands after the evaluation genuinely finished, and re-anchor the hold-fenced release comments: with the fence, only a Runtime that certified an abandoned evaluation as cancelled still triggers that machinery. * fix(cli): give the abandoned-evaluation fence a terminal transition Address PR #13243 review round 8 (its only finding, Critical R7-1). The abandonment fence published when a cancel lands mid module evaluation had no way out. Nothing revisited the receipt: entry.view is written only inside execute(), which has already returned; hook-status returns a clone of the stored view; and the late import settle handler cleared moduleEvaluationPending and then called an already-finished finish(). On the Harness side the only exits from recovery_blocked are settleExecution, which needs a result, and executionUnavailable(), which lists neither evaluation code. So a cancel that merely raced a healthy cold import — a window of roughly dispatch+200ms to evaluation end, because the Harness delivers a cancel through its 100ms poll loop — tombstoned the Session for the worker's lifetime: the import resolved, the callback provably never ran, hasHolds() went false and the slot and Broker hold were released, yet the record stayed recovery_blocked / outcome_unknown. Every later fire() rejected, drain() threw, DELETE and detach answered 503, no broker release was ever attempted, and the Workspace owner stayed pinned. At the merge base and at the previous commit the same input settled as cancelled and the Session stayed usable and deletable. Republish the receipt once the evaluation definitively ends, with what is then provable rather than a single code for both arms: a cancelled result when the module loaded, since dispatched is still false and the callback demonstrably never ran, and managed_hook_handler_unavailable when the module itself failed. The republish happens only after moduleEvaluationPending clears, so no receipt is certified while top-level code is still running, and it is keyed on the abandonment code so it can never overwrite another receipt. The record contract already allows recovery_blocked to step to cancelled, and status() does not short-circuit a recovery_blocked / outcome_unknown record, so the next status or drain reconciles it. A never-settling evaluation keeps the fence and its recovery barrier, which is the case the previous commit was accepted for. Both design doc versions now state the clearing transition beside the sentences that bound the hold. Pin: a sibling of the abandonment case whose module finishes its top-level code shortly after the abort asserts both halves — fenced with managed_hook_module_evaluation_abandoned and hasHolds true immediately, then cleared to a cancelled receipt with hasHolds false — and that the callback's marker was never written. Mutation-checked: deleting the republish reds this case on its 3s poll while the never-settling sibling stays green. * fix(cli): republish the evaluation-timeout fence and total the import settle path Address PR #13243 review round 9 (Criticals R9-1 and R9-2). R9-1: settleAbandoned() republished only the abandonment fence, so an over-budget cold module load — one that overruns max(manifest timeout, 500ms) and then finishes — kept its recovery_blocked / outcome_unknown record forever: hasHolds() went false when the import resolved, but nothing rewrote the view, so every later status re-committed outcome_unknown, fire() and drain() threw, and close() threw before releasing the Session's own Runtime owner, tombstoning the Session. At the merge base the same module simply loaded late and ran. Widen the republish guard to both evaluation codes, and publish the truthful outcome for the timeout arm: 'timeout' rather than 'cancelled', since nothing was cancelled there and HookExecutionOutcome keeps the two distinct; both reconcile identically at the host. Both design doc versions already promised the transition for both fences; their cancelled-only phrasing now names the timeout outcome. R9-2: importHookModule dropped the promise derived from import().then(...), and the rejection handler's new Error(String(error)) is not total, so a handler module whose top level rejects with a value String() cannot convert (Object.create(null), a revoked Proxy, a throwing toString) threw inside the handler, escaped as an unhandled rejection, and killed the session's Runtime worker — Node's default, with no unhandledRejection handler on this path — mid tool-call. At the merge base the same input settled as managed_hook_handler_unavailable. Convert under a guard so the primary settle path cannot throw, and give the derived promise a terminal .catch that clears moduleEvaluationPending, republishes managed_hook_handler_unavailable, and finishes with the same code, so a throw inside either settle handler settles the operation instead of escaping. finish's own guard keeps the terminal handler from re-settling an operation the timer or abort already settled, and the publish stays ordered after moduleEvaluationPending clears. Pins: the over-budget case now asserts the republished timeout receipt after the hold releases (it previously asserted the receipt stayed identical), a new case settles a module that rejects with Object.create(null) as managed_hook_handler_unavailable while a process-level unhandledRejection listener records nothing, and a host-side companion reconciles a timeout-fenced record through status(), drain() and close() once the view carries a result. Mutation checks: restoring the single-code guard reds the over-budget case on its 3s poll; restoring the bare String(error) with no terminal handler reds the rejection case at the 500ms floor with the wrong fence code and an escaped rejection. Each layer alone also carries the rejection case — removing only the .catch or only the guarded conversion stays green, as the finding's own flip measured — and the pair is shipped because the finding prescribes both a total primary path and a terminal structural backstop. * fix(cli): name the Hook owner-hold refusal and reconcile fences on the turn path The hold-fence predicate matched the two catch-all 409 codes, so any non-hold refusal (an identity mismatch or an unexpected provider error) was absorbed as a fence and wedged close() into a permanent recovery-required error for a Session holding no Hook work. The two genuine pending-work producers now answer with a dedicated managed_runtime_owner_hold_pending code and only that code fences; every other refusal propagates as before. A module evaluation fenced as outcome_unknown was reconciled only on the DELETE-path drain, so a live Session refused every later turn even after the Runtime republished the receipt. The turn path now polls each unsettled record once before refusing a new occurrence — status only, never a re-dispatch — so the fence clears on the next turn. * test(cli): deflake the fixed container port bind in container entry tests The deterministic gate rejected the previous round: both container-mode tests in managed-runtime-container.test.ts failed with EADDRINUSE on 0.0.0.0:43190. The fixed container port lies inside the Linux ephemeral source-port range, so under --maxWorkers=25% another worker's outbound fetch can hold 43190 while these tests try to bind it; both pass solo. Bind a real loopback-ephemeral socket through a Server.listen mock — the pattern the ACK-ownership suite in managed-runtime-attestation-worker.test.ts already uses for the same container-mode path — and keep the fixed-port pin as a call-argument assertion (mutation-probed: port 43191 turns it red). All route and attestation assertions still run against the real server. * fix(cli): reconcile fenced Hook records at the prompt gate and forward the owner-hold code - forward managed_runtime_owner_hold_pending through the Java runtime broker's 409 switch so the dedicated refusal reaches the Harness - poll exactly the pending Hook records once (non-cancelling, children via their occurrence marker) before the prompt admission gate, and settle the turn an evaluation fence parked, so a Session whose cancel landed mid-evaluation recovers instead of staying blocked for life - report the routine owner-absent release refusal on the debug logger - drop the never-read asError guard; cover the reject-after-fence republish with a Runtime test * fix(cli): keep a refused close's wake pump alive and bound the prompt-gate reconcile - Defer the monitor wake scheduler teardown until the Hook close is known terminal, so a DELETE/detach refused by an owner hold leaves the kept Session delivering monitor wake turns (wakeBusy still blocks mid-drain turns through mcpClosing). - Move the prompt-gate fence reconcile behind body validation and the idempotent replay, and give the pass a 30s budget plus a client-disconnect abort, so a stalled Runtime cannot hold the route open and a refused prompt never pays Runtime RPCs or durable writes. - Share the reconciled-fence predicate between the pre-model and pre-tool settle arms, so a republished PreToolUse evaluation fence settles its parked turn the way the pre-model arm already did. - Pin the absent-owner release refusal on the debug logger, settle the leaked async-child poll loop in its test, assert the abort's rejection identity, and size the admission-quota fixture from MANAGED_HOOK_MAX_RUNNING. * fix(cli): bound the fence-settle scan by occurrence and abort signal - Filter settled PreToolUse records by their in-memory occurrence id ahead of any durable read in the pre-tool settle arm, so a recovery pass no longer pays one resources.read per historical record. - Treat an unreadable or unparsable hook result as no fence, so one corrupt historical record cannot fail session open with managed_session_open_failed. - Thread the prompt-gate reconcile's abort signal through settleCancelledHookTurn so the 30s budget and a client disconnect bound the whole pass, not just the Runtime poll. - Pin the witnesses: no result read for an unrelated occurrence, totality on a missing result resource, abort identity on both settle arms, the disconnect handler ending the reconcile and releasing hooksBusy, and the fenced-owner refusal never logging the absent-owner wording. * fix(cli): re-arm the prompt-gate hook settle while a Session stays blocked - Widen the prompt-gate reconcile to fire while session.blocked, not only while Hook records are pending, and skip the poll when nothing is pending: a pass could drain the last record and still drop the settle (the shared abort fires between poll and settle, or a concurrent cancel's settle declines while hooksBusy is held), and blocked is cleared only inside settleCancelledHookTurn, so the Session refused every later prompt for its attached life (R4-2). - Admit a republished InstructionsLoaded evaluation fence by its creation inside the parked turn — a creation commit carries the record id as its operationId, revisions commit as <id>:<revision> — instead of cancelRequested, which a republished receipt can never carry, and keep historical InstructionsLoaded receipts behind the arithmetic pre-filter instead of paying a durable read per record per pass (R4-1). - Read Hook inputs tolerantly in both settle scans so one unreadable or corrupt input resource skips the record instead of rejecting the whole recovery pass (R4-5). - Correct the design doc's exclusivity claim for 409 managed_runtime_owner_hold_pending in both languages: the code also answers for unfinished non-Hook work the record-based release gate does not inspect (R4-4). * fix(cli): keep Hook recovery fences typed on the fire and close paths An unguarded reconcile ahead of the pending-gate throw let a generic durable-store or broker error escape fire() while the fence stood, so the turn was committed as a definite error and the recovery barrier never latched; absorb the poll failure, report it, and let the fence re-check answer with the typed recovery error. In the shared-broker shape the recovered-owner release loop bypassed the absorbing release helper and the fence check sat above it, so a hold-fenced recovered owner escaped close() as a raw Broker refusal and stranded every owner behind it; route the loop through releaseOwner and name the fence after the loop. Co-authored-by: Qwen-Coder <[email protected]> * fix(cli): retry fenced Hook owners on the close path before naming them Co-authored-by: Qwen-Coder <[email protected]> * test(cli): pin an empty QWEN_HOME for the cli test suite The autofix verification gate failed 389 tests across 13 files when the shared runner's real ~/.qwen/settings.json held invalid JSON: unpinned settings reads (loadSettings, createMinimalSettings, ...) parse the operator's real files. test-setup now pins QWEN_HOME to an empty per-worker temp dir, matching how it already isolates other operator env state. settings.test.ts mocks the filesystem outright and keys its fixtures on the os.homedir() mock, so it drops the pin before its module-load path constants are captured. * fix(cli): keep genuine callback timeouts out of the Hook fence classifier Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> * fix(cli): keep genuine callback-phase cancellations out of the Hook fence classifier Co-authored-by: Qwen-Coder <[email protected]> * test(cli): drain the crashed wake park before teardown in hosted-harness tests The 'refuses a prompt retriably when the wake park probe read nothing' test ended with its crashed automation wake park deliberately unsettled, so the session's wake pump kept retrying the settle past teardown; under parallel-file load the retried failures logged into later tests' stderr spies and failed their blanket no-log assertions (reproduced pre- and post-merge in ~2/3 of 8-file batch runs; the owning test was confirmed with temporary owner-stamp instrumentation). - Drain the parked settle (journal-witnessed turn.settled) before the delete, matching the file's sibling pattern; the delete then answers 204 and closes the scheduler. - Scope the two blanket writeStderrLineSafe never-called assertions to lines naming the scenario's own turn/operation id. - Retry the session-root rm in afterEach against in-flight pump writes (ENOTEMPTY race observed once pre-merge). Co-authored-by: Qwen-Coder <[email protected]> * fix(cli): unstrand the runtime hold when pathToFileURL throws (#13243) A synchronous pathToFileURL throw escaped the importHookModule executor before the derived promise existed, so none of the three moduleEvaluationPending clearing handlers was ever attached and hasHolds() stayed true for the worker's lifetime (Windows UNC paths pass manifest validation and then throw). Convert the href inside a try ahead of arming the flag, and pin it with a test that mocks node:url to throw. Also restore the two strict writeStderrLineSafe assertions in the Hosted Harness no-tool suite: quiesce each test's wake pump in afterEach via DELETE /session/:id (bounded, so a Session fenced behind a never-answering Runtime poll cannot wedge the hook) instead of narrowing the assertions around a leaked pump, and drop the rm maxRetries workaround the leak had forced. --------- Co-authored-by: qwen-code-ci-bot <[email protected]> Co-authored-by: qwen-code-dev-bot <[email protected]> Co-authored-by: qwen-code-autofix <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: qwen-code-autofix <qwen-code-autofix@localhost> Co-authored-by: qwen-code[bot] <qwen-code[bot]@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <[email protected]> |
||
|
|
c81e0b4a9b |
feat(managed-agent): Introduce the EventTransport seam behind the after-commit publisher (#13909)
* feat(managed-agent): Introduce the EventTransport seam behind the after-commit publisher Phase MQ1 of the cross-node EventTransport design: name the adapter point behind the after-commit hook as EventTransport and route publication through a single composite bean — local SessionEventHub fan-out first (unchanged failure semantics), then default-empty EventTransport adapters, each isolated so a failing adapter never blocks local post-commit SSE. No broker, no configuration, no behavior change; MQ2 plugs in additively. Part of #12380 · Refs #13200 * feat(managed-agent): Pin the MQ1 seam contract points from review Review follow-ups: pin batch pass-through, the drop log's tenant/session/sequence identity, and container collection of EventTransport beans; record the EventTransport implementer contract (synchronous on the committing thread, RuntimeException-only isolation, borrowed list); sync the slice doc and narrow the parent design's pre-MQ1 present-tense claims, both languages. |
||
|
|
a8990042e6 |
feat(managed-agent): admit public foreground Shell with mandatory approval (#13674)
* feat(managed-agent): admit public foreground Shell with mandatory approval * codex: address PR review feedback (#13674) * codex: address PR review feedback (#13674) * codex: address PR review feedback (#13674) * fix(managed-agent): keep Shell action answers with the recorded owner A Workspace operator may still answer other Actions. A Shell answer runs a command, so that arm does not apply. The rollout note says to upgrade the Harness before enabling the flag, because an older Harness ignores suppressChildAgents. * fix(managed-agent): state that only the owner may answer Shell Actions The public contract still said any Workspace operator could respond. Shell answers stay limited to the recorded owner; the route text now says so. * fix(managed-agent): omit the message funnel when Shell suppresses child agents Session messages share the child orchestrator's write chain, so a Shell lane that does not attach that orchestrator has no message funnel. A message-stop load keeps suppressChildAgents. * fix(managed-agent): require Shell approval before lifecycle and message-stop loads These two loads already send the Shell profile, but they skipped the check that a persisted Shell uses default or auto-edit approval. * fix(managed-agent): isolate internal TeamLead admission Preserve public Shell approval, child suppression and lifecycle boundaries while trusted internal TeamLead creation pins a reserved profile and private execution policy. Isolate public/internal creation replay and move the existing MySQL process rig to the trusted admission. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
7cbb993ccd |
fix(managed-agent): recover original Hook sessions during Workspace retirement (#13673)
* fix(runtime-broker): recover original Hook sessions during drain * fix(runtime-broker): drain absent original workers before Hook detach * fix(workspace): release stopped Hook holders under the drain claim * fix(runtime-broker): initialize the stopped Session holder schema * test(workspace): preserve the unknown execution reference identity * test(workspace): persist unknown execution through its dispatch claim * codex: address PR review feedback (#13673) * fix(runtime-broker): match the lease snapshot to the maintenance column V60 adds maintenance_id to the execution lease, and the broker schema snapshot has to carry the same column or the Flyway parity check fails after merging main. |
||
|
|
7cc2054a7c |
docs(managed-agent): H4e-b3 member commands, plan approval and shutdown design (#13912)
Designs the last H4e runtime slice: a member-to-lead command channel (a new session_command domain on H4d-b's relay, executed on the lead's team funnel), member board tools under Legacy's rules, member-to-member messages relayed by the lead to running members, asynchronous plan approval decided by the lead's model with team_plan_approval, and shutdown as a lead-side stop. team_message stays unproduced and legacy import is a non-goal. Implementation lands in four batches, b3a to b3d. The H4e-a, H4e-b1, H4e-b2 and H4d-b designs and the H4a and H4b delivery maps gain pointers; H4e-a's team_plan.requestId and team_message texts follow the new decisions. |
||
|
|
263d47e29d |
fix(managed-agent): unwedge Hosted stream close and bound task cancel attempts (#13877)
* fix(managed-agent): unwedge Hosted stream close and bound task cancel attempts The H4f real-stack acceptance (#13746) found a task cancel that never retried or parked. After a Hosted Harness restart behind a half-open connection, the first call to the new Harness got the generation 409, and adoptGeneration closed the stale client. HarnessEventStream.close() then waited on the ReentrantLock that a Turn's reader held across a blocking SSE read that never returns, so the cancel attempt hung inside the adoption. Its claim kept renewing the lease every 20 s, the Session's operation slot stayed held (close answered 409 session_operation_active), and the claim budget never advanced. - HarnessEventStream.close() no longer takes the reader's lock. Closing the response body is what ends the blocked read, and that read now reports the stream as closed rather than as the server ending it. getLastEventId() reads a volatile field instead of waiting on the reader. - TaskCancelCoordinator runs each send on its own thread and waits for it at most five minutes, or four Harness request timeouts when that is longer. A send still running then is logged as abandoned and counts as an unproven outcome: the delivery reads the record and retries or parks, and its lease renewal stops. An abandoned send that lands later is judged by the record like any lost reply. - The H4f design doc records the real-stack round and states the bound: 608 s measured when attempts fail fast, and about 90 minutes computed at the default timeouts if every attempt hangs. It also lists the open close-stops-children item (#13875) and a Hosted stream heartbeat. Refs #13746 * fix(managed-agent): evict an unverified attachment and log abandoned sends Review follow-ups for the task cancel attempt deadline: - The abandoned send is interrupted, and an interrupt can make the connector's approval-mode check throw after a cold attach already cached its Harness ref. doCreateOrLoad and doRecoverManagedRuntime now evict the ref they minted (and its recovery marker) when that check throws instead of answering, so no later verb takes an unverified attachment. A ref verified earlier stays cached, as before. - An abandoned send logs how it ended, failure or return, so an operator can tell a late refusal from a late landing. - sendWithinDeadline documents that its executor must stay unbounded. - HarnessEventStreamPinningTest and HarnessEventStreamCloseTest share one fake Harness (HeldStreamHarness), and two new cases pin the post-read closed check: a close racing a buffered frame and a close racing the end of the body both report the stream closed. * fix(managed-agent): flag every abandoned send and pin its logs Second review round on the attempt deadline: - An interrupted delivery abandons its send like a timed-out one, so the interrupt branch also marks the send abandoned before cancelling it, and the send logs how it ended on both paths. - The abandoned-send failure WARN carries the throwable, so the cause chain (where the abandoning interrupt shows) reaches the log. - TaskCancelCoordinatorTest captures the coordinator's log: the timed-out send logs its return once released, a late failure logs its cause, and an interrupted delivery logs its send's end too. - QwenHostedHarnessConnectorTest pins the other side of the eviction: a ref verified when it was attached stays cached when a later approval-mode check throws, on the plain attach and the recovery load. * docs(managed-agent): count the abandoned-send log tests in the H4f design |
||
|
|
f8c99414b2 |
fix(core): report monitor startup failures as tool errors (#13460)
* fix(core): report monitor startup failures as tool errors * fix(core): classify monitor startup failures consistently |
||
|
|
7f496aca15 |
test(managed-agent): Add FG7 channel fault gates for the channel domains (#13802) (#13839)
* test(managed-agent): Add FG7 channel fault gates for the channel domains (#13802) The channel_route and channel_delivery domains are enabled since #13572, but nothing yet proves the channel side effects stay honest across lost answers, mid-send process deaths, and ambiguous provider replies. FG7 pins them in FG6's shape: real packaged binaries, a fault-injecting proxy, a real MySQL lane, and one named mutation per gate. - FG7a/b/d/e/f in HostedChannelFaultGatesIT (15 cases, in-JVM Spring, SQL trigger store faults, per-letter -Dqwen.fg7X.case selectors); FG7c in HostedChannelProcessCrashIT + HostedChannelCrashFixtureMain (4 cases, killable child-JVM control plane and adapter). - One parametric TS driver (hosted-channel-fault-driver) with a content-aware lose-once proxy, the real ManagedEmailAdapter against scripted FakeImap/FakeSmtp, and a recording wrapper over the production HttpManagedChannelControlPlane so the wire shape cannot drift from production. - FG7e gates read-ledger continuity instead of an SSE surface that does not exist (cursor pagination stays newest-first, dedup-free); FG7f gates the unknown family; partial waits for H5d's multi-segment plan. - CI budget raised fork 900s to 1200s, Verify step 25 to 35 min, job 148 to 160 min, pinned by hosted-process-ci.test.js, plus a local hosted-channel-gates profile. 15 mutation red signatures verified by execution (the 10 designed ones plus M2/M5 collateral cases), all reverted to green. * docs(managed-agent): FG7 header and validation wording audit follow-ups Open-ended audit of the FG7 diff found two wording drifts left by the review rounds: the driver file header still described the hand-rolled thin-HTTP client the wave-2 fix replaced with a recording wrapper over the production control-plane client, and the surefire count in the validation bullets read as an at-HEAD number although the rebase moved the baseline (main added unit tests since). Wording only; no behaviour. * test(managed-agent): fail the FG7e reads when the parked driver fails The two FG7e parked-pull branches asserted `driver.waitFor(...)` completes and went straight to the JDBC/read assertions without checking the driver's exit value. A driver exiting 1 at its settle-oracle (for instance the exact send/receipt counts introduced in this PR) could therefore leave both `delivery-page-resume` and `delivery-state-progress` green — the other parked-pull branches propagate either through `exitValue()` or through the success-only results file. Both branches now assert `driver.exitValue() == 0` with the driver log attached; a sabotaged driver (exit 1 after settle) redness-witnesses the propagation, reverted to green. Also corrects mutation-table entry 8 in both design languages: the production claim discovery is the two Java layers (`listSessionsWithPendingDeliveries` + `findPendingDeliveries`), not `pendingDeliveries()` (unit-test callers only); the executed mutation was the compound of both discovery filters plus the funnel `claim` terminal refusal weakened together, and only that compound reaches the wire. * test(managed-agent): FG7 review wave fixes and the local suite blockers Answer the second review wave (R1-3 to R1-47 suggestions) and two reproduction blockers the rerun forensics found locally: - Positive controls on silent paths: cancel-planned/replay assert a claim pull reached the control plane; the out-of-plan-ordinal gate asserts the 409 envelope code; receipt-reply reads the ledger state and provider receipt; the resend case asserts segment identity; all ten vacant-sends sites go through assertSendsEmpty. - Evidence hygiene: per-letter and per-class case roots, per-launch driver tags, awaitLog writer-liveness, drive() reaping in finally, caseLogs inclusion of fixture logs, fg7-cases artifact upload. - Launch reliability: --server.port=0 for the in-JVM Spring and the crash fixture public listener; the harness bound port must match the reservation; -Dnode.executable asserted before boot. - spring-kill-dispatch: holdUntilFile keeps the deceived adapter ticking across the bounce and the driver ticks after release, so the warm claim relay reaching the restarted control plane is real (postBounce) instead of vacuous. - The workflow's path filters now cover packages/channels/email and packages/cli/src/commands/channel, mirrored in the pinned test; the job headroom moves with its 13-minute estimate; the hosted fork budget is pinned literally at 1200. * test(managed-agent): FG7 review follow-ups on positive controls Second round of answers to the suggestion wave, all verified green twice on the full FG7 suite: - relayCountOk counts only relays whose answer is < 400 for the positive controls (adapter liveness, outbox pulls, re-drives); a 4xx/5xx answer at the proxy is not evidence the adapter kept working. The drop counters stay on relayCount. - assertSendsTo pins the recipient of every physical send on the exactly-once cases, so a misdirected re-drive cannot pass on counts and messageId alone. - spring-kill-dispatch re-reads both unknown projections after the cold resume, matching its sibling re-checks. - The crash fixture's /control/reconcile and /control/expire report a failing call as 500 with the exception text instead of an unconditional 204, so a reconcile-side failure fails at the control() call with the cause attached. * test(managed-agent): answer the R2 review wave for the FG7 channel gates Second suggestion wave (R2-1 … R2-27) answered across the driver, both IT classes, the workflow and the design docs, verified green twice on the full FG7 suite before commit: - Positive-control hygiene: the postBounce witness now only counts a healthy answer (status < 400) past the bounce mark; both crash-class cold-start zero-send legs assert the cold adapter's claim pull provably reached the control plane beside the no-send check; spring-kill-dispatch re-reads ledger and input counts after the warm window so the widened window has its own duplicate-effect witness. - Evidence hygiene: drive() launches keep the DriverLaunch tag so consumer names stop being re-derived as -1 literals at all 25 sites; the two pull-terminate waits went through the writer-liveness-aware awaitLog on FG7_AWAIT_KILL, preserving the ordering the Files.exists poll relied on; caseLogs is total (safeRead + a bounded fallback) in both classes; await surfaces the last probe failure in its timeout report instead of a bare deadline; the driver persists its results file before propagating a failing phase; the exact-once and zero-send witnesses are one helper each, called from both exit paths; FG7_RELAY_FAILED prints the actual fetch cause, not just "fetch failed". - Docs truthfulness (both languages): the guard actually gated is the missing-segment refusal surfaced as 409 channel_operation_conflict — the dense-ordinal store rule stays reachable only under H5d's multi-segment plans; the fault-fired mechanism list covers all 19 cases; the FG7b trigger lifecycle names the per-class teardown that isolates it; the adapter-kill row names the resend assertions that actually belong elsewhere; FG7e's ledger rows state the frozen-state setup, the separate post-release re-read, the per-delivery route and the planned-minted-at-claim visibility rule; the driver inventories carry holdUntilFile and FG7_RELAY_FAILED, and the marker list splits sequencing markers from diagnostic log lines; evidence filenames get their per-launch tags; the acceptance bullet narrows to what actually ran (compilation only for the Java-side files, not Checkstyle / SpotBugs on test sources). - CI: packages/channels/base/** and packages/cli/src/commands/channel.ts added to both workflow trigger lists and to the pinned test's list; the fg7-cases artifact path is pinned in hosted-process-ci.test.js with its module-qualified string next to the budgets it moved. A note on diagnosis recorded for honesty: the first replay of the full suite ran red across all five letters on an unmodified batch, with the same \"managed email admission ... was refused (400)\" seen on earlier runs; instrumented ApiExceptionHandler.invalid() captured the cause as IllegalArgumentException(\"Unsupported JSON value type: ObjectNode\") from JsonSupport.writeCanonical in the shared m2 alpha jar, which had been rebuilt by a parallel worktree whose alpha build clobbers it. The tests never saw that before because the failure window is the jar's rebuild window; this tree's own qwencode module predates that canonicalizer — its JsonSupport encodes the same body without that rejection. After a straight reinstall from this branch, fg7a alone passed in 8.2 s and both full-suite runs are green — this red was never in the R2 diff. * test(managed-agent): FG7 post-review exit-witness and post-bounce hardening Answer the three R3 review threads on the channel fault gates: - The parked-pull waits shared by cancel-sending and out-of-plan-ordinal asserted waitFor completion without the exit code, but since the driver's catch now persists its results file before propagating a failure, a driver-side assertion failure leaves a readable results-<tag>.json and then the case proceeds to passes at its coarser checks anyway. The sibling exit-value zero assertion closes that: a driver that dies with its own quota/identity check still makes the case fail because results were just written but nothing asserts what it contained. - spring-kill-receipt now mirrors spring-kill-dispatch's positive control for the receipt verb: a warm receipt relay with a healthy status (status < 400) must land after the bounce, measured by bounceDone instead of any relay that happened with the server gone. bounceDone is declared outside the try block so the witness compiled beside the witness reads (same shape as the existing twin). |
||
|
|
e6ed740eb1 |
feat(mods): add TypeScript authoring and configuration (PR2a) (#13890)
* feat(mods): add TypeScript authoring and configuration * fix(mods): preserve marketplace dependencies and update notices --------- Co-authored-by: qqqys <[email protected]> |
||
|
|
186193e250 |
fix(managed-agent): settle worktree child runs after their merge (#13753 I2 follow-up) (#13872)
* fix(managed-agent): settle worktree child runs after their merge (#13753 I2 follow-up) Follows up the review deferrals of #13841. A stopped worktree run kept its child Workspace. The stop arm (H4f) settled the run cancelled without a finish request, so the row stayed ready with the worktree on disk. It now asks for the discard before its close_scope, or for the merge when the child's task Turn had completed before the stop. A failure was told before the merge landed. The over-bound, give-up and later-failed-Turn arms settled the run failed while the merge they asked for ran afterwards. A merge the relay asked for now holds every settlement of the run (a failure, a stop or a give-up, not only the result) until the row records its outcome. A child whose task Turn completed keeps its work when a later message turn fails, its message wait runs out or a stop cancels it, and the relay records that merge as soon as it sees the task Turn of a bound child completed, so a parent's close meanwhile pins the work. The foreground answer and the recovery gap fill of a started worktree run that failed or was cancelled end with a line telling the parent's model to inspect its Workspace and where unlanded work is kept: the record has no receipt to carry the outcome. A merge waited on every heartbeat on a host that runs no child Workspace steps. It now waits at the close-debt cadence there, as on a host without the close capability, and the merge is asked even where the child cannot be closed yet. A parent's close discarded a completed child's work unpinned when its discard replaced a merge that had not started, or one that ended blocked before its pin. Such a discard is recorded as discard_pinned and pins the child's result before removing the worktree. The pin never holds the discard back: when no result can be taken (a non-retryable refusal, or a failure on the last retry) the discard goes on as it did before. No contract change and no migration: discard_pinned is a new value of the existing finish_request column. * fix(managed-agent): settle lost-creation worktree runs without an unbound finish (#13872) Review follow-ups for the child workspace settlement round: - stopChild and the give-up chain never record a child Workspace finish on a row whose child was never bound (a lost creation answer): the stop binds the lineage-named child first, and the give-up discards instead of asking for a merge it cannot wait for without re-entering itself. - The ended-run workspace line claims the merge's ordering, never its landing (a conflicted, blocked or pinned-away merge means the work did not land); both render sites now share childRunEndedText. - The owed-merge wait logs what it holds for and no longer re-reads the child result text or re-asks the merge on every pass. - One isDiscard predicate for the two discard spellings, one pinResult for the two take-and-pin sites, a witness for capable(), and the discard-pin fallback now logs the full key set and records last_error. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
edec849aa9 |
feat(managed-agent): H4e-b2 the lead's mailbox over Session messages (#13892)
* feat(managed-agent): H4e-b2 the lead's mailbox over Session messages Implement the H4e-b2 design (#13879) on top of H4d-b's Session messages and H4e-b1's team, with no record, rule, route, migration or Java change. - A member is its continuation chain. The roster names the chain's first run; task_list, team_delete and the owner rule read the chain head. sendToChild continues a finished member of an active team, which #13846 refused until this slice; a closing team still refuses, and a former member continues as a plain child. - The lead's send_message beside the team tools takes to (a member name, or "*" for every member) or task_id. A name routes as its first run's task id does under the same ids; a broadcast commits one message or continuation per member under bcast_/msgb_ ids that no single send or launch can mint, and a batch that broadcasts cannot change the roster. - Every run of a member's chain opens its instruction with a fixed preamble naming the member and its team and saying that only the answer to the last instruction reaches the leader. It counts toward the envelope bound, and a replay accepts the committed instruction with or without a well-formed preamble block. - A member's message to the lead and a continuation's result carry the <teammate> label while the chain root is on the open team's roster; the reply hint stays H4d-b's task id. - task_update delivers nothing. The owner rule accepts a member given work earlier in the same Turn even if it has completed since, keeps stopping and stop-requested heads off open work, and its refusals never claim which task a past send carried or offer to skip a send for work that never arrived. - Recovery and the PostToolUse dispatch evidence probe a send_message's base ids and every broadcast position. The H4e-b2 design now records the implementation; the E2E plan lists the physical pass that runs before merge. * fix(managed-agent): settle the round-1 audit findings on the H4e-b2 mailbox - The owner rule checks a never-started newest continuation before work given in this Turn: an earlier send of the Turn cannot vouch for work the latest continuation never delivered. - A task id of a continuation that never started routes from its predecessor, so an answer that named it still reaches the member. - A replayed send by name answers with the member's name and label only while its team is open; a broadcast replay whose team was deleted or replaced answers for the recipients it reached and sends nothing new. - One chain index serves the roster view, a broadcast and sendToChild, which now shares the member views' head definition; the PostToolUse dispatch evidence probes broadcast ids without indexing the lead's runs. - A member's byte_limit refusal names the preamble; dead team helpers go; the board and roster descriptions match the owner rule. - Tests cover the edges the round-1 mutants survived: stopped members given work this Turn, a kept owner given work, a cancelled message, never-started notes in answers, the active cap in a broadcast, the input order behind the approval preview, and broadcast dispatch evidence after a restart. * fix(managed-agent): settle the round-2 audit findings on the H4e-b2 mailbox - sendToChild returns, on its writes chain, what an answer must tell about the run a send reached: the open-team member, a continuation that never started before the one it launched, and, on a replay, its own run proven never started since. Answers by name, by task id, on replay and in recovery all read it, so a member addressed by task id is named, a replayed or recovered send that never arrived says so, and the note no longer misattributes a later continuation to a replayed broadcast. - A never-started continuation's note, roster line and owner refusal count the messages it lost, and tell a continuation a user cancelled before its start apart: its work is asked for again only if still wanted. - Refusals to a member name the member; an ended one points to a new member under a new name. - Recovery of an interrupted broadcast counts a never-started recipient as not reached and lists ended members apart. - task_update's notice acknowledges a send earlier in the turn. - chainHead throws instead of falling back to its input. - Tests for a rejected message, a launch not counting as work given, the launch-side broadcast batch rule, the encoded input order, a broadcast reaching no member, broadcast replay after delete or replace, admit evidence, and the recovered never-started send. - The design text follows. * fix(managed-agent): settle the round-3 audit findings on the H4e-b2 mailbox - The owner rule records an ended member on the task a call completes when any run of its chain completed, as #13846 records who did the work; a member that never completed a run is pointed to the member that did it, not to a new member. - task_update's answer for a restated owner, which is not checked again, reads the member: a newest continuation that never started is asked for again, and an ended member is never pointed to a send. - A never-started continuation's work and the messages it lost are asked for again together; a replayed or recovered send asks again only for its own message, says "your resend" when it did not arrive either, and a message to a launch that never started says the member has ended. - A member stopped before its continuation attached is refused with a text that waits for it to settle instead of pointing to a new member. - A broadcast sends to no further member once its turn is cancelled, and a partial answer asks for sends by name; recovery of a broadcast whose team changed since names no new roster. - Tests for the guards the audits found unpinned, and the design text, including the never-started task id route and the index note. * docs(managed-agent): quote the H4e-b2 partial broadcast answer as implemented * test(managed-agent): expect H4e-b2's owner refusal in the Hosted team IT * docs(managed-agent): record the H4e-b2 physical acceptance results * fix(managed-agent): read a message's delivery before answering it as reached A committed message whose delivery failed did not arrive, though its run did not prove it never started: cancelled before its handover, rejected by its target, or still waiting for a handover the relay cancels because its run ended or is being stopped. The route details now read the message's delivery, so an interrupted broadcast's recovery no longer counts such a recipient as reached (an ended member is listed as unable to take it, and a live replay notes it on the member's line), and a replayed or recovered single send says the message did not arrive. The answer points to a resend while the chain takes messages, to waiting while a continuation stopped before its start settles, or to a new member (a new agent for a plain child) once the chain has ended. The H4e-b2 and H4d-b designs say so. |
||
|
|
fce50611b6 |
fix(managed-agent): connector and broker robustness from the #12692 R2 review (#13330)
* fix(managed-agent): connector and broker robustness from the #12692 R2 review Nine follow-ups from the post-merge R2 review round on #12692, re-verified against current main ( |
||
|
|
23274922f5 |
feat(web-shell): add Send now for queued mid-turn messages (#13681)
* feat(web-shell): add Send now for queued mid-turn messages
A message typed while a turn runs waits for the model response that is
streaming and for its tool batch, so a correction typed into a long answer
reached the model seconds later, and Stop was the only faster path.
Typing keeps that behavior: the message stays queued, editable and
deletable until the next tool boundary. A new Send now action on a queued
row asks the daemon to deliver the queued messages at once.
POST /session/:id/mid-turn-messages/send-now, advertised by
session_mid_turn_send_now, sends the agent craft/midTurnSendNow. While a
foreground turn streams a response that has not started a tool call, the
agent drains the user's messages, cuts the response short, keeps the part
already streamed, and answers in the same turn. Running tools are never
interrupted, a response that started a tool call is never cut, queue-only
steering is not taken early, and a request is scoped to the turn that
received it. Input taken by a turn that is then stopped or fails is kept
in that turn's conversation.
The TypeScript SDK gains sendMidTurnMessagesNow on both clients.
* fix(web-shell): address review findings on Send now
- Restart the Stop-hook state when a continuation answers input that
cut its response short, so the next Stop check is not hook-forced and
the consecutive-block count and reasons start over.
- Give input a finished turn keeps unsent a 2 s resolution deadline of
its own; the deadline also cancels media bridges.
- Requeue a failed send-now drain in its original order, and log a
send-now request the agent rejects.
- In Web Shell, reconcile a row whose message already left the queue
and ignore a repeated click while a request is out.
- State the tool-call carve-out wherever Send now is described, and
document goal turns, the narrow no-answer window and token accounting.
- Add tests for the review's gaps, drive the drain deadline with fake
timers, and add the route to the multi-workspace failure tables.
* fix(web-shell): keep send-now input a stopped tool boundary leaves unbuilt
When a turn is stopped while a tool-boundary drain resolves a message,
the drain now hands back the messages it has not built that an early
send-now drain took, so the end-of-turn keep records them. Input the
boundary drain took for itself is still dropped there, as before.
- Tests: the stopped boundary keeps that input once and carries nothing
into the next turn; a Goal continuation is cut; a background
notification turn is not; a failed send-now drain does not requeue a
message that left the queue meanwhile.
- Bridge and SDK contracts: the cut applies to a foreground turn's
streaming response, and `requested: true` does not mean a cut.
- Protocol and design docs: the timed-out send-now drain, resending a
settled id, the 10-message drain limit, and the tool-boundary drop.
* test(web-shell): pin send-now behavior questioned in review
- Assert that cron and background-notification turns never open their
response to interruption and that a send-now request arms nothing
while they stream.
- A response cut short by queued input still answers that input when it
ends with an error, while an error from a response its drain did not
cut remains the turn's error. Explain in `watch` and the design doc why
the cut response's error is absorbed: the cut does not always arrive as
an AbortError, and the response was given up.
- A message whose deletion missed while a failing drain held it is still
delivered, which matches the `{ removed: false }` answer the client
got. Document this in the protocol reference.
- Rename a requeue test to say it covers a queue-only entry.
* docs(web-shell): scope the abort invariant and drain-held removals
- The main prompt loop's catch states that other AbortErrors surface.
Note that a response cut short by queued input reaches that catch only
when the turn itself is stopped meanwhile, because `watch` absorbs
whatever a cut response throws.
- In the force-drain paragraph, add the drain-held case of
`{ removed: false }`. Such an id must not be re-posted, and its listing
in `settledMessageIds` is not final, because a failed drain returns it
to the queue without an event.
|
||
|
|
bb7de6aeed |
test(managed-agent): run the H4e-b1 team flow end to end in the Hosted MySQL lane (#13867)
* test(managed-agent): run the H4e-b1 team flow end to end in the Hosted MySQL lane
HostedTeamLeadMySqlIT drives a real Spring server on MySQL, the packaged
Hosted Harness, the embedded Runtime Broker and the child result relay,
with a scripted model, through three cases the physical pass covered:
- a lead creates a team, launches two named members, keeps a board, is
refused team_delete while they run (the refusal writes nothing), takes
each member's report under its own label, completes the tasks (one
owner kept, one set only once its member finished, the refused open
ownership writing nothing) and deletes the team;
- closing the lead cancels its running member: through four refused
cascade attempts the close stays owed, the member's Session stays
active and its Turn keeps running; the Turn then completes on its own,
the team records stay as they were, and once the relay is done the
lead holds no acceptance of the result and has made no new request;
- in default approval mode each team write and the named launch asks on
its own card, previewing its own input, and task_list asks nothing.
The lead is created through the store and moved to the Shell profile in
the same transaction, because no public route creates a Shell-laned
Session; everything after that goes through the public routes. Each case
closes its lead and waits until the tenant owes no operation, child
result or Turn, because the operation recovery and the relay scan every
tenant and the next Spring context in the fork would pick that work up;
a failed case ends that work in its teardown. Each Session's Broker
worker is durable and outlives Spring, so the cleanup HostedPublicWorkspaceIT
used moves into HostedDurableWorkers and both ITs call it.
The Hosted ITs share one 900 s fork. A fixture failure, a failed lead
Turn or member, a report without its label or a lead without the team
tools ends a case at once. Awaitility ignores the interrupt a @Timeout
sends, so every wait goes through a helper that fails on it, which makes
each case's ceiling (80 s, about 20-26 s healthy) a real one. The CI row
in hosted-process-ci.test.js pins that every case has a ceiling, that
all of them together fit the fork beside its healthy runtime even if
every case stalls, and that no wait bypasses the helper; the healthy
runtime the Hosted rows assume grows to 650 s with this class. A failed
case prints the Harness log tail, where a failed wake Turn shows.
* test(managed-agent): close the Hosted IT Spring test contexts after their class
The Hosted*IT classes share one failsafe fork and one MySQL schema, and
Spring caches a @SpringBootTest context for the rest of the fork.
HostedConcurrentTurnBurstMySqlIT's context has a fixture Harness but no
Runtime Broker, and its operation and Turn recovery scan every tenant, on
a 50 ms dispatch scan. When HostedTeamLeadMySqlIT ran after it in CI, that
context claimed the team cases' close operations and could not close their
Workspace Sessions ("This instance cannot verify the original worker
stop", "close-incapable host"): in the close case the member's close was
blocked, so the lead's close never completed, and the approval case's
lead close ended RECOVERY_BLOCKED.
Close that context after its class, and HostedWorkspaceRecoveryWorkerIT's
too (H2, but its schedulers otherwise run to the end of the fork). A
hosted-process-ci row now requires @DirtiesContext, AFTER_CLASS or the
default, on every Hosted*IT whose class declares a Spring test context.
* test(managed-agent): end a stalled burst round's work and harden the context guard
Now that HostedConcurrentTurnBurstMySqlIT's context closes after the class,
a round that stalled would leave its Turns and operations for the next
class's context to claim. End them in @AfterEach, before the context
closes, as HostedTeamLeadMySqlIT does for its tenant.
The hosted-process-ci context row now strips literals and comments in one
pass (a "/*" inside a line comment no longer swallows the class header),
reads the header from where a type declared before the class ends, accepts
@SpringJUnitWebConfig, an "@ " spacing, empty parentheses, hierarchyMode
and AFTER_EACH_TEST_METHOD, and still refuses BEFORE_* modes.
|
||
|
|
6936c77e60 |
feat(managed-agent): support WebShell session cwd changes (#13760)
* feat(web-shell): support managed session cwd changes Co-authored-by: Qwen-Coder <[email protected]> * feat(managed-agent): advertise WebShell cwd change capability Co-authored-by: Qwen-Coder <[email protected]> * chore(managed-agent): consolidate W2 WebShell delivery Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): compose abort signals without AbortSignal.any below Chrome 116 refreshSummary composed the caller signal with the hook lifetime via an unconditional AbortSignal.any, which is Chrome 116 / Safari 17.4 while the package floor is Chrome 111 / Safari 16.4. On those browsers a successful cwd change threw TypeError on the completion readback, was booked as unconfirmed, and left the composer locked for the lifetime of the tab. Mirror the composeAbortSignals feature-detect from acpTransportUtils and rebase onto main: the cwdChange capability becomes OpenAPI v1.36 (v1.34 is reserved for actor roles, v1.35 landed first). * refactor(managed-agent): address triage nits - managed.cwd.unconfirmedForbidden now tells the user sending stays blocked until the server settles the operation or a new tab is opened. - cwdShaped collects to HashSet explicitly since retainAll mutates it; Collectors.toSet makes no mutability promise. - Drop the never-read cwd field from ManagedSessionCwdControl basis state. * fix(web-shell): repair CI after rebase Prettier rejected the conflict-resolved OpenAPI JSON (the closing brace stayed glued to the description line), and the transcript entry grew past its byte ceiling: main measures 1,320,579 and this branch 1,323,120 against a 1,323,000 gate. The growth is the cwd slice's shared i18n strings; no interactive cwd code enters the transcript graph. Raise the ceiling to 1,324,000 with the measurements recorded, matching the gate's documented ratchet practice. * chore(managed-agent): renumber cwd capability to OpenAPI v1.37 Main took v1.36 for the replay-floor pass (#13621) while this branch was in review, so the cwdChange capability moves to v1.37 and the design docs follow. Also drop the pre-rebase SHA from the size-gate docblock; the measurement is unchanged at 1,323,120 on the rebased branch. * feat(managed-agent): converge cwd capability on the #13545 role model Main's #13545 moved cwd admission from creator identity to atLeast(OPERATOR) while this branch was in review. The capability predicate now mirrors exactly that: one role grant read covers the workspaces of the cwd- and Turn-shaped candidate union, the execution-facts read covers the union of both id sets, and the facts batch is skipped when the caller holds no OPERATOR grant — the same zero-query shape the submit path has. The OpenAPI entry becomes v1.38 (main took v1.37 for the roles slice). Capability tests move to the role vocabulary: a non-creator OPERATOR now sees cwdChange while a READER does not, and the no-facts-read cases run under a READER-only caller. Design docs record the convergence. * fix(managed-agent): drop duplicate registry-facts interface declarations Textual auto-merge with #13545 concatenated this branch's fail-closed default methods with main's abstract declarations of the same two AgentStateStore methods, breaking compilation. Main's abstract declarations win: ManagedAgentStore is the only implementation and overrides both. The design docs' fail-closed-default note goes with the removed methods. * fix(web-shell): release a parked cwd intent on definitive replay refusals A lost-ACK intent has no operationId, so Confirm is the only way out — but definitive was gated on initialSubmission, so a confirm replay could never classify a refusal as final and the composer stayed locked for the tab's lifetime. The store's actor-scoped idempotency lookup runs before the role, revision and busy gates, so a 4xx behind it proves the original submit was never recorded. Drop the initialSubmission conjunct and release with the real refusal code; 401 and the pre-lookup read-visibility 404 stay non-definitive (they prove nothing about recording), as do transient 408/429. The query path starts accepted=true, so transient mid-poll refusals still retain an admitted operation's intent. R1-12. * docs(managed-agent): align the cwdChange contract text with the role model The field description still said the caller must be the Session creator, contradicting the role-based predicate shipped in the same diff; the WebShell design docs still recorded #13545 as open with a pre-rebase baseline. Both now match the converged role model, and the generated client is refreshed. The composeSignals fallback test also pins the hook-lifetime half of the composition, not just the caller signal. R1-2, R1-3, R1-22. * chore(managed-agent): renumber cwd capability to OpenAPI v1.39 Main took v1.38 for the automation runtime (#13598); the cwdChange entry moves to v1.39 and the design docs follow. Transcript entry measures 1,323,162 against the 1,324,000 gate on the rebased branch. * codex: address PR review feedback (#13760) * codex: address PR review feedback (#13760) --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
504e4a7997 |
fix(managed-agent): reconcile approval delivery and concurrent session titles (#13682)
* fix(managed-agent): recover queued approval delivery after cache loss Keep accepted Workspace approval responses deliverable with the default recovery configuration after attachment-cache loss. Leave approval retry claims untouched on Harness-disabled replicas. Co-authored-by: Qwen-Coder <[email protected]> * fix(managed-agent): reconcile durable session rename delivery Fence late title writes by durable revision and replay the latest intent after ambiguous replies. Renew approval claims during slow attachment and settle journal-proven terminal answers without dispatching from disabled replicas. Co-authored-by: Qwen-Coder <[email protected]> * test(managed-agent): scope the held-delivery mock to its own action ActionResponseCoordinator.recover() scans every deliverable ACTION_RESPONSE operation across tenants, so the PENDING response left in the shared test database by disabledReplicaSettlesOnlyJournalProvenEndedResponses reached this mock and bumped the resolveAction counter, making calls.get() 2 instead of 1 on both the MySQL 8.4 and the MariaDB lane. Ignore resolveAction calls for other action ids, the same action-scoped shape the shared harness mock already uses via its responses map. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-conflict/jmuzuwh4lfc * test(managed-agent): extend the pinned migration list to V53 WorkspaceMigrationMySqlIT.upgradesCurrentMainWithoutChangingAppliedMigrations pins the versions applied on top of the V47 baseline. V53 was added by this pull request without extending that list, so the MariaDB lane failed with ["48", ..., "53"] against ["48", ..., "52"]. The failure only surfaced after the earlier ManagedActionsTest failure was fixed: the lane stops once the first surefire batch fails, and this class is in the second batch, so it never executed on the previous head. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-conflict/jmuzuwh4lfc * fix(managed-agent): fence title delivery ownership and migration admission Reject disabled or unsupported title requests before admission, claim inline delivery before I/O, and fence failed-owner cleanup against lease takeover. Include unfinished title deliveries in workspace migration idle checks. Co-authored-by: Qwen-Coder <[email protected]> * fix(managed-agent): renumber rename-delivery migration to V54 Upstream #13544 added V53__managed_workspace_roles.sql, so V53__managed_session_rename_delivery.sql collides on the pull_request merge tree and fails the "Flyway migration version uniqueness" gate. Renumber the rename-delivery migration to V54 and extend the WorkspaceMigrationMySqlIT expectation to 48..54, which is the version set the merged tree applies. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmv0iu3nwgo * test(managed-agent): declare fenced titles in workspace fixtures Keep the existing positive rename and creator replay fixtures compatible with the explicit title protocol preflight. Co-authored-by: Qwen-Coder <[email protected]> * docs(managed-agent): record title migration ordering after role sync Document V54 rename delivery following the main V53 Workspace roles and Session owner migration. The verified product and test tree reuses the concurrently pushed main-sync history. Co-authored-by: Qwen-Coder <[email protected]> * fix(managed-agent): renumber rename-delivery migration to V55 main merged V54__managed_child_lineage_relay.sql after this branch numbered its rename-delivery migration V54, so the PR merge tree had two migrations claiming version 54 and the Flyway uniqueness gate failed. Renumber the PR-side file to V55 and extend the pinned upgrade list in WorkspaceMigrationMySqlIT. Local check: node scripts/check-flyway-migrations.js packages/sdk-java/managed-agent-server packages/sdk-java/runtime-broker packages/sdk-java/qwencode -> "55 migrations, all versions unique". Java compilation is not available on this box (no mvn, JDK 1.8); CI is the verifier for the IT. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmv0kz9gvgr * test(managed-agent): register a Clock bean in the artifact API fixture context ManagedAgentService.configureRenameDelivery is an @Autowired setter that takes ManagedAgentProperties and a Clock. The mini context built by ManagedArtifactApiIntegrationTest.configureApi registered the properties bean but no Clock, so context.refresh() failed with UnsatisfiedDependency and errored all 26 artifact integration tests (ManagedArtifactApiIntegrationTest, plus ManagedArtifactReadIntegrationTest which reuses the same fixture through configureApi()). Production wiring already supplies the bean via ExecutionConfiguration.managedAgentClock(), so the fixture only has to mirror it. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-conflict/jmv0mrk9bgv * fix(managed-agent): retire exhausted renames behind a durable fence Preserve retained titles and block lifecycle until the remote retirement is acknowledged. Co-authored-by: Qwen-Coder <[email protected]> * revert: preserve confirmed managed rename recovery contract Revert commit |
||
|
|
90f066af56 |
docs(managed-agent): H4e-b2 team mailbox design (#13879)
* docs(managed-agent): H4e-b2 team mailbox design Pin the decisions for the second runtime slice of H4e (#13745) before implementation. Messages between the lead and its members travel as H4d-b Session messages: a member is a child run of the lead, so the lineage edge, the receipt with its input, the settlement hold, the stop over message turns and the close all apply unchanged, and team_message stays disabled. On top of that the lead addresses members by name and reaches every member with "*"; a member is its continuation chain, so the refusal #13846 added until this slice is lifted and a finished member takes more work and stays a member; every run of a member's chain opens with a fixed preamble naming the member, its team and how its result reaches the lead; and the lead sees a member's messages and continued results labeled with its name. task_update stays a board record: delivering from it would rerun work a finished member did, or cost a running member the report of its current work, and a replay could not tell whether it owed a delivery. Member-side commands (board tools, member-to-member messages) move to H4e-b3 with plan approval and shutdown, on one member-to-lead command channel. No record, rule, route, migration or Java change. The H4e-a design records the answers to its open questions 1, 2 and 4 and notes that task_assignment is not produced; the H4e-b1 design points to the re-scope, the lifted refusal and the answered open question 3; the H4d-b design's routing exception and H4e row point here. * docs(managed-agent): give broadcast ids their own namespace and cover a member that completes before its owner update Address the review on #13879. - A digest put in the call-id slot could equal a plain call's provider id, which would mint the same message and run ids. Broadcast run ids are now bcast_ plus a digest with no colon, while every single-send or launch run id is promptKey:callId; broadcast message ids start with msgb_, while single sends start with msg_. The two id sets cannot meet whatever call ids the provider hands out. A recipient's continuation or message names the broadcast call's key as its executionCallId. - One batch orders the send before task_update but cannot keep the member running: the relay settles a child while the lead's Turn runs. The owner rule now accepts, on an open task, a member given work in this Turn (one prompt id, all its model rounds and its resumption): an earlier call of the Turn committed a continuation of its chain that did not prove it never started, or a message to its head that was not cancelled or rejected. A head that failed, was cancelled after it started, is stopping or completed after a stop request is still refused. Refusals never claim which task a past send carried and never offer to skip a send for work that never arrived: a completed member gets one conditional text, and a never-started continuation its own. |
||
|
|
d534f90d32 |
feat(core): Keep complete file reads on one opened source (#13776)
* feat(core): keep complete file reads on one opened source Extract the ordinary file-source and PDF cancellation lifetime from #13526. Co-authored-by: Qwen-Coder <[email protected]> * fix(core): admit special and zero-length files before opening them Opening every inline read before classification could touch a tty, fifo, or device, and a reported size of 0 made procfs reads come back empty. Classify first and keep those paths on the pathname source. * fix(core): annotate admission-test predicates for tsc The const case rows made isFile's return type circular, so the package build failed before tests ran. * fix(core): keep successful PDF output and prior read rights A timer that fires before a child's close was reported as a timeout, descriptor cleanup could replace a finished render, and a failed re-read erased an earlier full read from the cache. * test(core): skip the descriptor render cleanup case off Linux and Darwin pdfDescriptorPath throws before the test's try on every other platform, so the Windows nightly suite would fail a case that only the descriptor path can exercise. --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
d35277c2f3 |
fix(runtime): retain CSI mount identity through directory resolution (#13772)
* fix(runtime): retain CSI mount identity through directory resolution Retain the original root descriptor across CSI observation and normalized directory traversal, join admitted work before close, and clean up the mount on worker startup failure and shutdown. Preserve failed acquisition cleanup as an observable error. Co-authored-by: Qwen-Coder <[email protected]> * codex: address PR review feedback (#13772) * test(runtime): pin the in-walk descriptor check against replace-and-restore Cover the interleaving wenshao's round-2 verification left unpinned (F3, MU4): a child replaced between the walk's lstat and open, then restored before the post-walk lstat. The descriptor-vs-lstat comparison right after open(2) is the only guard that refuses it; this test goes red when that guard is deleted. Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> * fix(runtime): re-validate the mount root before excusing an unlocatable sibling Round-2 review follow-up. The private-estate exemption now re-reads rootDirectory() at the moment an unlocatable sibling would be excused: a mount that fenced between the first root read and the sibling's resolution no longer flips the cross-session veto from deny to allow, for CSI fences and for ordinary-mount root replacement alike (R2-1). Tests: the round-1 witness now pins the refusal reason and adds a positive control so a wholesale execute-path failure cannot satisfy it (R2-2); a new in-loop case fences the mount inside the sibling resolve and goes red if the freshness conjunct is removed; a new cross-device case keeps the named stats real so only the descriptor-device arm can decide, going red when that arm is deleted (R2-3). Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
fa18c9fc83 |
feat(managed-agent): execute pinned AgentDefinition revisions (#13530)
* docs(managed-agent): design AgentDefinition execution (D8b and D8c)
Proposes how a Session pins a stored AgentDefinition revision (D8b) and how
the definition's fields take effect (D8c): permission_policy and tools through
the existing per-Session approval mode and tool profile pins (Java only), then
model and instructions through the Hosted Harness. Content that cannot take
effect refuses admission instead of being dropped. The D8a design's open
questions now link here.
Decisions for review are listed in section 7.
* docs(managed-agent): address D8b/D8c design review
- Default the definition tool-profile allowlist to files/1; files/2 becomes
an operator opt-in after the #13166 rollout, since Java has no switch.
- Add W1 recovery's source validator as the fourth qwen-code check and keep
the pinned revision and digest in the recovery source.
- Refuse approval_timeout_ms in v1 instead of accepting a value the
connector would not deliver.
* feat(managed-agent): pin and compile stored AgentDefinition revisions (D8b+D8c-1) (#12867)
Session admission resolves a stored definition: an explicit agent_revision
pins that revision, an omitted one resolves the latest revision locked
under FOR UPDATE in the creation transaction, and a missing agent or
revision answers 404 agent_not_found. The pinned (agent_id, revision,
digest) lands on the new agent_definition_digest column (V53), and replay
returns the original Session without resolving again.
D8c-1 compiles permission_policy and tools into the approval mode and
tool profile the Session already pins: shell profiles never pair with
yolo, unbound Sessions stay model-only, and selectable profiles sit
behind the qwen.managed-agent.definition-tool-profiles allowlist
(default hosted-workspace-files/1). model, instructions, skills,
mcp_servers and environment_template_id refuse with 409
agent_definition_unsupported naming the field in error.field instead of
being dropped.
The four hard-coded qwen-code gates relax to qwen-code-or-pinned:
later-Turn admission, Workspace execution authorization and W1 recovery
sources, which now carry the pinned revision and digest. PublicSession
gains the optional agent_digest and the contract becomes 1.34. Two
design amendments ride along: FOR UPDATE implements the stated locked
read (H2 has no FOR SHARE) and the refusal field is named error.field,
matching the envelope shape.
* chore(web-shell): regenerate managed agent API types for error.field
* test(managed-agent): include V55 in migration upgrade assertions
The MariaDB integration run applies the new definition-digest migration correctly. Keep the strict ordered upgrade expectation in sync while preserving the applied-history checks.
Co-authored-by: Qwen-Coder <[email protected]>
* fix(managed-agent): correct three review findings on stored-definition execution
- insertChildSessionCommand now copies agent_definition_digest from the
parent, so a child of a stored-definition Session is no longer born
with a NULL digest (non-executable, and poisoning W1 recovery).
- hosted-harness-session: a deadline expiry keeps naming the deadline;
only a generic error outcome is reclassified as model_unavailable.
- README and the definition-service javadoc no longer claim Sessions do
not use stored definitions.
Co-authored-by: Qwen-Coder <[email protected]>
* fix(managed-agent): accept published agent instructions in W1 recovery
The W1 recovery reader's PUBLISHED allowlist covered only the three
managed-tool-result kinds, so reading a definition Session's digest-
addressed instructions resource threw resource_layout_unsupported and
failed the whole storage's capture/verify (R1-3). The reader now also
accepts managed-agent-instructions up to 64 KiB, mirroring the
allowance ManagedSessionStore.verifyStoredResource already carries,
and the mixed-storage capture test publishes non-empty instructions
through the production publisher and reads them back through the
recovery RPC.
Also fixes two test/doc findings from the same review round: the two
compiler-test @CsvSource text blocks were passed as value, so JUnit
executed one row per annotation (14 of 16 rows dead) — now textBlock =
(R1-50); and the OpenAPI route descriptions for createAgent and
createSession (plus their WebShell twins) still claimed Sessions do
not use definitions and public callers cannot select the profile,
which this PR's own admission changes falsify (R1-7, R1-6). WebShell
generated types regenerated.
* fix(managed-agent): round-2 review — migration version, stop-load pin, store guard, doc truths
R2-3 (fails-closed): the new migration took V58, below versions main
already shipped (V60/V62/V64), so with Flyway's default outOfOrder=false
it resolves-but-never-applies on any upgraded database and the
agent_definition_digest column every Session INSERT and read touches
silently does not exist. Renamed to V66, the next free version.
R2-1: messageStopAttachment is the third LoadHarnessSession site and the
only one that never carried the pinned agentDefinition, so a
definition-pinned child Session's message stop was refused with 409
hosted_agent_definition_conflict. The load now carries the pin, and
aMessageStopOfAPinnedSessionCarriesTheAgentDefinition pins it red/green.
R2-2: Session admission pinned a stored definition even when the Managed
Session Store the Harness create needs was disabled — a supported-looking
config (harness on, store off, files off) answered 202 for a Session that
could never run. validateWorkspaceFiles now refuses that shape at startup,
and ManagedAgentPropertiesTest witnesses it.
R1-28: two of the four falsified statements were still standing — the
README's revision-source sentence (a pinned Session records the revision
resolved at admission, not QWEN_MANAGED_AGENT_REVISION) and the
AgentDefinitionRequest javadoc's 'no field changes Session execution yet'.
Also from the same round: the 64 KiB agent-instructions bound is now one
named constant (MAX_AGENT_INSTRUCTIONS_BYTES) instead of four bare
literals across publisher, both verifiers and admission (R2-5); the
createAgent description's stale (v1.34) label is now v1.41 and names the
operator allowlists that gate model and tool profile (R2-13), the
webShellCreateSession description no longer promises a fixed server-owned
profile for definition-pinned Sessions (R2-6), and the workspaceTurns
predicate wording now matches the implementation (active and undeleted,
both branches on the frozen execution profile) (R2-11); the generated
managed-agent-api.ts was regenerated.
* fix(managed-agent): pin V66 in the workspace migration IT list
|
||
|
|
ca08a3d152 |
feat(managed-agent): Collect retired stream-capture tool outputs (#13534) (#13554)
* feat(managed-agent): Collect retired stream-capture tool outputs (#13534) Co-authored-by: Qwen-Coder <[email protected]> * fix(managed-agent): Address stream-capture collection review findings (#13534) - Drop the page-exit exhaustion probe: eligibility is fetched with LIMIT PAGE_ROWS + 1 and the overflow row answers "more pages" directly, halving ELIGIBLE executions per page. - Log a blocked ledger once per gc_blocker transition (previously only persisted, never surfaced); the design doc names the per-claim log line as the P1 observation surface. - WorkspaceRecoveryReader answers resource_collected for inline rows whose bytes the O4-2 publication collector freed while leaving state REFERENCED, matching the README and design-doc contract; those rows misreported resource_corrupt before. - Pin the claim scan's gc_next_at >= 0 predicate with a comment (measured 0.013 ms vs 1,779 ms at 1M completed ledgers). - Correct the design and lifecycle docs (EN/zh): the ledger-creation scan runs at a 60-second cadence with a 32-candidate oldest-first admission bound, the shared single-thread output scheduler now hosts two collectors plus the observer, and gc-enabled gates both collectors. - README and operations docs name the tool-publication.enabled precondition and note that freed bytes stay inside the InnoDB tablespace until an explicit OPTIMIZE TABLE or rebuild. * fix(managed-agent): Skip already-freed rows in stream-capture accounting (#13534) The ELIGIBLE predicate never required the bytes to still be present, while the ledger adds byte_length, which is metadata that outlives the bytes. A row already freed by another writer without a state change would be selected again and counted a second time on the permanent audit ledger (the sandbox verifier measured 1500 counted vs 500 freed). Add the one-predicate guard, pin it with alreadyFreedRowsAreNotCountedAgain, and sync the design doc's eligibility enumeration in both languages. * fix(managed-agent): renumber stream capture migration to V51 and tighten collection verdicts (#13554) - Rename V48__managed_stream_capture_collection.sql to V51: main took V48-V50 via W1c, and the merged tree booted Flyway with two V48 migrations. All prose references (both doc languages, operations docs, README) follow the new number. - Gate the ledger-scan cadence on an injectable monotonic clock (System::nanoTime) so a wall-clock step back cannot silently stall ledger creation, and pin the re-scan with a one-instance cadence test. - Answer resource_collected from positive evidence of collection (row COLLECTED, or a cataloged publication object COLLECTED in scope) and leave unexplained byte loss on the fail-closed resource_corrupt path. - Correct the design doc baselines in both languages: O4-2 already frees cataloged REFERENCED bytes; the sealed manifest is journal-referenced and pinned permanently; the never-cleared guarantee covers layout invariants only; collected_bytes sums byte_length metadata. Co-authored-by: Qwen-Coder <[email protected]> * fix(managed-agent): renumber stream-capture migration to V53 and pin the collected-state probe (#13554) Co-authored-by: Qwen-Coder <[email protected]> * chore(sdk-java): clear generated javadoc residue failing the lint gate (#13554) The deterministic gate's npm run lint scanned untracked, gitignored Maven javadoc output under packages/sdk-java/qwencode/target/reports/apidocs (generated by a package-phase build in the shared checkout) and failed with 589 no-undef errors. The repair is the working-tree cleanup of that regenerable residue; this commit carries no tracked content change and preserves the rejected V53/probe commit unchanged. * docs(managed-agent): name both pre-V53 verdicts for collected recovery reads (#13554) Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> * fix(managed-agent): block stream-capture collection during in-flight recovery (#13554) - Add the recovery_active blocker: a CAPTURING or VERIFYING workspace recovery reads a retired Session's bytes through WorkspaceRecoveryReader without a read lease, and no lease can exist for a tombstoned Session, so the collector now joins managed_workspace_recovery_session to live operation state before collecting; it reschedules on the 60-second class like reader_active. - Move the phantom-row guard off the page query: referencing inline_bytes in WHERE makes InnoDB materialize every candidate's BLOB (up to 101 rows per page) just to test it for NULL. The page UPDATE keeps AND inline_bytes IS NOT NULL and collected_bytes now sums only the rows the UPDATE actually moved (maintainer-verified candidate patch). - Pin the resource_collected positive-evidence probe's scope_key and resource_id predicates with a test (maintainer's candidate test; kills the surviving N08/N14 mutants). - Docs (EN/zh): eligibility, the blocker list and the acceptance criteria name recovery_active; the operations doc notes that under MySQL's default binlog_row_image=FULL each collection UPDATE also copies every freed BLOB into the binlog. Co-authored-by: Qwen-Coder <[email protected]> * fix(managed-agent): close stream-capture claim→page recovery race, renumber ledger migration to V56 (#13554) Co-authored-by: Qwen-Coder <[email protected]> * fix(managed-agent): terminate captures on collected publication bytes, renumber ledger migration to V57 (#13554) A collected publication object surfaced as publication_object_unverified, which is in neither invalidating set, so the capture wedged in CAPTURING and its recovery_active blocker pinned the retired Session's collection forever; map the COLLECTED marker to the terminal resource_collected at the read. Rename the ledger migration V56 -> V57 after main landed V56__managed_operation_actor_key, and pin the collector's transactions to REPEATABLE READ so the page-time recovery fence keeps its gap locks on servers defaulting to READ COMMITTED, with InnoDB witnesses on the mysql-integration lane plus an H2-runnable assertion of the pin. Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: Shaojin Wen <[email protected]> * test(managed-agent): drive a genuinely ACTIVE head in liveHeadBlocksCollection (#13554) The fixture's UPDATE rewrote SEALED over the insert's own SEALED, so the test never exercised the ACTIVE head its name promises, and a future narrowing of the session_head_live predicate to only ACTIVE would have shipped green. Maintainer verification round 1, suggestion 2. Co-authored-by: Qwen-Coder <[email protected]> * test(managed-agent): serve the stream-capture MySQL suite from one schema per class (#13554) The MariaDB integration job (timeout-minutes: 15) was killed at 15m17s on the merged head. The suite this IT inherits runs ~52 tests, and each test created its own database and re-ran all 57 Flyway migrations, so the bootstrap — not the assertions — dominated the job's budget once main's child-workspace tests joined the same run. Create one schema per class, keep every test and assertion on real InnoDB, and isolate tests by truncating every table but flyway_schema_history after each test instead. The FK toggle and the truncates share one connection because FOREIGN_KEY_CHECKS is session-scoped and the data source hands out a fresh connection per call. Co-authored-by: Qwen-Coder <[email protected]> * fix(managed-agent): renumber stream-capture migration to V65 (#13534) Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: qwen-code <[email protected]> Co-authored-by: qwen-code-dev-bot <[email protected]> Co-authored-by: qwen-code-dev-bot <[email protected]> Co-authored-by: qwen-code-autofix <qwen-code-autofix@localhost> |
||
|
|
66ddb151cb |
feat(managed-agent): enable the H4e-b1 team domains after the physical pass (#13846)
* feat(managed-agent): enable team_state and team_task after the H4e-b1 physical pass * fix(managed-agent): let a finished member own the task the lead completes * docs(managed-agent): record the H4e-b1 physical pass and the enabled team tools * docs(managed-agent): note the close wait, the Shell-profile reach and the pure control arm * test(managed-agent): drop the team gate lifts that the enablement makes moot * fix(managed-agent): keep a finished owner off open tasks and settle the round-one review notes * fix(managed-agent): name the kept owner when a reopen is refused and settle the round-two review notes * docs(managed-agent): point open question 5 at its tracking issue * docs(managed-agent): note isolated team members and the landed message runtime #13841 lets a named member run with isolation "worktree" and #13822 landed H4d-b. Record what that changes for the lead's team: the isolation argument on the team's Agent tool, the prepare and merge steps of an isolated member's relay, its run counting as running until the merge records an outcome (so team_delete stays refused for that unbounded wait), its completed state meaning settled rather than merged, and that the physical pass did not cover isolated members. Fix the step team_delete waits for (the settle, step 5). * fix(managed-agent): refuse to continue a finished team member and show member task ids #13822 lets send_message continue a finished child as a new run. For a team member that run would sit off the roster: task_list would keep showing the member completed, team_delete would pass while it runs, and its result would arrive without the teammate label. Until H4e-b2 lets the roster follow a continuation, sendToChild refuses to continue a member's run by name; a running member still takes messages. The task_list roster now ends each member line with the task id that send_message takes, and a failed or cancelled run with its stop reason, so a member the host never started (for example an isolated member whose Workspace was not prepared) reads apart from one that failed its work. With a team, send_message's refusal of a name points at those task ids instead of the legacy tool, and the board answers mention send_message for a running member. Add the isolated-member test #13841 left out (both labels and the settle team_delete waits for), and record in the design and the E2E plan that the physical pass predates #13841 and #13822, that H4f's public cancel has landed, and the other statements the two merges made stale. * fix(managed-agent): let a former member continue once its team is deleted The continuation refusal read every roster, deleted teams included, so a former member stayed refused forever, and its advice to launch a new member pointed at a team that no longer existed. Once a team is deleted nothing reads its roster, so a former member now continues as a plain child with its result unlabeled; the refusal applies only to members of an open team and says so. Pin that a plain child beside a team still continues, and that a former member continues after team_delete. Note the exception in H4d-b's routing decision, say in task_list's description that a member's task id addresses it while it runs, and tidy two test texts. * fix(managed-agent): share the open-team check and show task ids only for running members Review follow-ups on the team enablement: - One open-team predicate (isOpenTeam: active, or closing until deleted) for openTeam() and for send_message's refusal to continue a finished member, through a new hostedOpenTeamMembership. hostedTeamMembership keeps its unfiltered answer for the label, replay and recovery readers, so a result accepted after its team was deleted keeps its label. - managedChildTaskId in core: every Hosted CLI derivation of a child task id (launch answers, taskIdOf, the notification, the roster) calls it, and the cross-language fixture pins it. - task_list's roster shows the task id only for a running member, the one send_message can reach. The continuation refusal says when a new member can be launched and what deleting the team costs. The board notices and the by-name refusal point to the id task_list shows beside the member's name, and send_message beside the team tools states the member rule. - Docs: the H4e rows of the H4a, H4b and H4d designs; the open-team exception wherever H4d-b states its routing rule; decision 3 no longer says nothing reads a deleted team's roster. |
||
|
|
9763580b84 |
feat(mods): run experimental headless Mod commands (#13843)
* feat(mods): run experimental headless Mod commands * fix(mods): preserve marketplace hooks and packaging fixtures * fix(mods): skip ignored Claude hook paths --------- Co-authored-by: qqqys <[email protected]> |