mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-10-21 05:40:14 +00:00
70 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
97ebffaa70
|
feat(managed-agent): support Plan mode and inline attachments (#14531) | ||
|
|
e986a74dfc
|
fix(sdk-java): keep the snapshot flag when a load copy restates child Workspaces (#13905 item 40) (#14519)
The one-argument form of the load request's child Workspace setter passed false for the snapshot capability, so restating child Workspaces on a copy that served snapshots silently dropped them. Every other copy of the request keeps the flag. The one-argument form now keeps the flag the copy carries, so only turning child Workspaces off drops it, as the two-argument form already does. No control-plane caller uses the one-argument form today; this removes the trap for the next one. |
||
|
|
398e916d70
|
test: fix the SDK Java suite's red jobs (FG6e frames, schema V8 pin, client flakes) (#14457)
* test(managed-agent): expect tool progress frames in the FG6e SSE gap gate #14405 projects a committed tool intent and its receipt as public item.tool_call.updated events, so a hosted tool turn now publishes five events instead of three. The FG6e driver and probe still pinned the old shape: depending on when the in-progress update reached the store, the early observers saw frame 2 before the tool finished or the finished-phase ledger check met two unexpected tool updates. The gate now waits for the in-progress update before disconnecting and expects ids 1-2 before the tool finishes, 3-4 after it and 5 at the terminal. * test(sdk-java): fault only the caller's dispatch in the send-escape tests The three pre-dispatch escape tests armed a one-shot executor fault and let the next execute() take it. A SelectorManager task trailing the previous response can call execute() first on a loaded runner; the fault then kills the HttpClient's selector, the real send fails with an IOException, and the test sees PromptAdmissionUnknownException instead of the injected throwable. Only the calling thread's dispatch takes the fault now. * test(managed-agent): expect schema V8 in the SSH workspace MySQL IT #14318 pinned the fully migrated managed schema at version 7 in this integration test, and #14404 then added V8. The two merged independently; the MySQL lane, which only runs in the SDK Java full workflow, now fails all twelve cases in the setup assertion. The other version pins already moved to 8 with #14404. * test(sdk-java): widen the keepalive idle-budget margin The keepalive test sent a comment every 120 ms against a 500 ms idle budget, so any half-second stall in the server writer or the client's body delivery on a loaded runner tripped the watchdog. It failed that way on ubuntu-latest / Java 17 (SDK Java run 39018177648). The budget is now 1500 ms and the consumer stays parked for over two budgets, which keeps the test proving that keepalives alone hold the stream open. * test(sdk-java): keep tiny-executor setup traffic off the injected rejection The one-worker executors used a two-slot queue, so a burst of HttpClient tasks during createSession on a slow Windows runner was rejected before the behavior under test (run 39021209055). Saturate an unbounded queue only once the test arms it, and keep the submit-time rejection tests from rejecting a selector-thread task trailing createSession, which aborts the client. * test(managed-agent): expect FG6e tool progress as transcript items, not events The transcript query keeps item.tool_call.updated out of its events and materializes it as a tool_call item, like text deltas (run 39021209055). * test(managed-agent): drop unused test imports that fail Checkstyle --------- Co-authored-by: qwen-code-bot <qwen-code-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
6c7c044533
|
fix(managed): Recover cancellation after process restarts (#14377) | ||
|
|
18a27de0b3
|
test(sdk-java): read the heartbeat scheduler queue on its own thread (#14427)
A periodic task leaves the ScheduledThreadPoolExecutor queue while it runs, so an outside read can see an empty queue before the 404 has retired the attachment. The drain check then exited early and its second read failed (0.072 s on an ECS Java 17 leg). Sampling on the single scheduler thread rules out a running heartbeat task. |
||
|
|
2b6de671d1
|
feat(managed-agent): Add SSH Tool Runtime support (#14318)
* feat(managed-agent): add SSH Tool Runtime support Add administrator-configured SSH placement, remote Workspace registration, Session-isolated workers and strict retirement over the existing HTTP protocol. Keep new admission disabled by default and preserve original-worker identity. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(managed-agent): clean up SSH runtime transport lifecycle Keep bootstrap work outside the endpoint lock and release unused forwards and stopped direct routes without disconnecting durable observers. Bound control work and complete queued futures during shutdown. Align SSH registration with the shared SQL clock, reuse storage identity and dependencies, and fix the environment inventory and transcript budget. Normalize inherited CSI test formatting for Java verification. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(managed-agent): align lifecycle and timestamp test fixtures Use canonical SQL timestamp columns and database milliseconds in fault-gate evidence, and require parent close to cancel a held child Turn as current main specifies. Preserve production behavior and capability boundaries. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(managed-agent): Bind environment query IDs as binary Preserve canonical Session and Turn identity types when H2 recompiles the latest environment query. Force recompilation in the existing regression without changing its admission-order or environment assertions. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
d565d7463d
|
fix(sdk-java): Close Hosted Harness review criticals from #12654 (#13314)
* fix(sdk-java): Close Hosted Harness review criticals from #12654 #12654 merged before its second review round was processed. This closes the 11 Critical and 2 Minor findings, each re-verified against main and pinned by a new HostedHarnessClientTest case whose mutation witness was run (reverting the fix reddens the test): - HarnessEventStream close() no longer parks behind a next() blocked in the socket read: the closed flag is atomic, the raw input is closed without any stream monitor, and HostedHarnessClient.close() completes; the blocked consumer fails with DaemonTransportException (R1-1). - The hosted SSE transport gains the idle watchdog its stdio sibling has: Builder.sseIdleTimeout defaults to 45s, and a silent peer makes next() fail with "Hosted Harness SSE idle timeout" (R1-43). - submitTurn releases the active-prompt slot on a definitive 409 and throws PromptAlreadyActiveException; retaining a marker keyed by the rejected promptId wedged the session client-side (R1-2). A definitive non-409 failure on a same-identity retry releases that slot too (F2). - getStatus() snapshots the entry before the remote read and removes only that registration, so a stale hasActivePrompt:false cannot delete a different prompt admitted in flight (R1-4). - Responses missing the X-Qwen-Harness-Boot-Id fence header on mutation paths are classified outcome-unknown (create/prompt/mutation/delete) instead of a bare DaemonProtocolException (R1-5). - A local executor rejection (saturated or terminated pool) surfaces as DaemonTransportException instead of claiming the request may have reached the daemon (R1-45). - closeSession(ref) and detachSession treat a definitive 404 as done and tear down the heartbeat timer (R1-3, R1-46). - streamEvents falls back to the session ref's validated watermark when the builder omits the resume pair, so the epoch fence actually applies (R1-44). - The shared SSE parser accepts the id-less session_snapshot frame, so StreamHarnessEvents.snapshot(true) works (R1-47). - computePayloadDigest hashes a sorted-key canonical form, so the wire dedup key is reproducible across JVM processes (R1-6). - HarnessRuntimeExecutionRecovery requires status for known outcomes at construction instead of NPE-ing in isContinuationReady() (F1). Test: HostedHarnessClientTest 34/34 (15 new); full module 181 green with 9 env-gated skips on JDK 17 and clean test green on JDK 11; 14 mutation witness arms all red. The remaining 49 Suggestion-level findings are dispositioned in their review threads and tracked in #13313. * fix: Close second-round review criticals on the Hosted Harness client Addresses the CHANGES_REQUESTED round on #13314, which execution-proved one real regression in the first commit plus five more critical defects around it: - R1-1 (the regression CI confirmed on the fault-gates lane): the digest was canonicalized but the wire `prompt` member was not, and the hosted route re-derives the digest from the received bytes — every addText submission drew `400 invalid_hosted_prompt`. toJson() now emits the canonical form, the digest reuses the JsonSupport.encode facade over that same value, and the test asserts the wire bytes and the digest are literally the same value. - R1-3: the 45s idle watchdog was inherited from the stdio transport, whose server heartbeats; the hosted events route wrote nothing while no event was committed, so healthy silent streams (tool calls, the 10-minute approval wait) were aborted. The hosted route now mirrors the primary route's keepalive comment every 15s, and Builder.sseIdleTimeout accepts Duration.ZERO for callers that own the deadline. A keepalive-only peer is covered by a new test (consumer parked in read for three idle budgets). - R1-4: an idle abort while the consumer was between next() calls surfaced IllegalStateException (not the DaemonException family). The closed guard now distinguishes who closed the stream: idle abort -> DaemonTransportException("Hosted Harness SSE idle timeout"), caller close -> IllegalStateException, matching the javadoc. - R1-5: the cause-chain scan certified any rejection-shaped IOException as "never dispatched", but delivery also runs on the caller executor, so a rejection after the server accepted the prompt dropped the active-prompt marker for an admitted turn. Only the bare RejectedExecutionException (submit-time) and the terminated-executor case now classify as saturated/known; everything mid-flight stays outcome-unknown. A deterministic delivery-phase test drives this via a package-private executor seam (park the worker, fill the queue, then answer 202). - R1-6: the missing-boot-header wrap reclassified every unfenced response as outcome-unknown, including definitive 4xx the server emits itself before the fencing middleware (413, 429). All four sites now re-derive: ambiguous (408/5xx) keeps outcome-unknown, definitive unfenced statuses surface as DaemonHttpException with their status preserved. - R1-7 (suggestion folded in, same hunk family): detachSession tears the local attachment down for every definitive answer, not just 204 and 404 — a 403/400/410 no longer leaks a heartbeat timer. - R1-13 (suggestion, adjacent): a caller queued on cursorLock when the stream closes re-checks inside the lock and gets the closed-stream signal instead of a transport error. Also: the snapshot frame comment now names the actual producer (the bridge's withSnapshot on the primary events route — never the hosted route); the StreamHarnessEvents builder javadoc documents the lastEventId fallback precedence and that snapshot(true) is a primary-transport option; the README retry section and both design-doc languages now state the canonical digest as a cross-language wire contract and record that changing the digest algorithm requires quiescing or rewriting in-flight turn rows (R1-2's deployment note); the two 10ms heartbeat tests assert liveness instead of comparing zeros; and an explicit-cursor-wins case plus a definitive boot-headerless DELETE case close the two remaining unpinned arms. Test: HostedHarnessClientTest 42/42 (full module 189 green, 9 env-gated skips) on JDK 17; clean test green on JDK 11 (2 additional version-gated skips: JDK 11 parks such sends instead of surfacing the rejection); hosted-harness-session.test.ts 181/181. Nine mutation witness arms all red (revert toJson canonicalization, forget idle signal in the closed guard, drop the in-lock re-check, unbounded cause-walk, blanket boot-header wrap at prompt and DELETE sites, detach without finally, watchdog removed with the keepalive arm staying green, ref-only resume fallback). * fix(sdk-java): close third-round review criticals on the Hosted Harness client (#13314) - submitTurn: a definitive unfenced refusal (a response without a valid boot header that is not an ambiguous 408/5xx) now releases only the active-prompt marker the call owns, in both the validateGeneration arm and the post-consume arm; a refusal produced before the route's promptId lookup can no longer delete an earlier same-identity entry whose outcome is unknown, and a refused fresh submission no longer wedges the session. - send: snapshot the executor termination state before dispatch so a close() racing an in-flight send keeps its outcome-unknown classification instead of certifying non-dispatch. - StreamHarnessEvents: an explicit lastEventId cursor now always wins, including a deliberate 0 (replay from the beginning), which HarnessCoordinator passes for turn rows without a recorded watermark; only an omitted cursor falls back to the session ref's watermark. - HarnessEventStream: the idle watchdog now measures peer silence only while a consumer is parked in next(), so time between calls (a blocking queue handoff, a slow event handler) no longer aborts a healthy keepalived stream. - docs: the payload digest is re-derived by the server from its own re-serialization of the parsed prompt member (JSON.stringify form), not from the received bytes; the README qualifies cross-release digest stability and the keepalive floor for a custom idle timeout. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk-java): close fourth-round review findings on the Hosted Harness client (#13314) - Carry the wire status and machine-readable code through PromptAlreadyActiveException instead of collapsing every 409 into a fixed message - Keep a prompt marker whose submission is still in flight from being cleared by a status answer that predates the admission - Map a watchdog scheduling rejection to DaemonTransportException and drop the stranded stream registration - Document the enforced idle-abort window (budget to 1.5x) and the peer-side backpressure limit on consumer pauses - Pin the fenced definitive-refusal release, the Duration.ZERO watchdog opt-out, the next() entry stamp, and the terminated-executor release with dedicated regression tests * fix(sdk-java): close fifth-round review findings on the Hosted Harness client (#13314) - Retain the active-prompt marker when a fenced 409 carries hosted_prompt_recovery_required: that code proves the promptId is in the durable input log, so releasing the marker would admit a second identity into a session with two unsettled inputs and fail every recovery entrance closed; every other 409 code still releases it - Clear the hosted events keepalive from stop() as well as from the close handler, and send X-Accel-Buffering: no so a buffering intermediary cannot swallow the keepalives the client watchdog relies on - Pin the settled status-read clearing, the failed-close unregister, and the disabled watchdog's empty scheduler queue with positive witnesses, and extract the shared fenced-SSE response prelude in the client tests - Correct the documented idle-abort window with the 100 ms poll floor, state the digest's sha256: wire encoding in the README, and narrow the PromptAlreadyActiveException javadoc to what the code does * fix(sdk-java): lifecycle teardown runs only on definitive answers Close the corridor the next-round review's close/detach receipts queued: local attachments (and their heartbeat timers) must not survive a definitive refusal, and must NOT be torn down on outcome-unknown. - detachSession: the unconditional `finally { removeAttachment }` also ran when requireMutationStatus surfaced unexpected-success as MutationOutcomeUnknownException — a state where the server's answer is not decided and the lease must keep beating. Teardown now runs on the definitive limb (204 or tolerated 404) and inside the DaemonHttpException arm only; outcome-unknown surfaces keep the attachment (anUnexpectedDetachAnswerKeepsTheAttachment). - closeSession(HarnessSessionRef) and closeSession(String): mirror the same try/catch — a definitive DELETE refusal (e.g. 403) now also retires the attachment and the active-prompt ledger instead of leaking the timer until close(), while ambiguous answers keep them (aRefusedCloseStillTearsDownTheHeartbeat ×2 overloads, anUnexpectedCloseAnswerKeepsTheAttachment, aBootHeaderlessCloseKeepsTheAttachment for the gateway-502 shape). - closeSession(String) gains the ensureOpen() guard every other public entry has (a deferral receipt plus #13313's R1-50): the closed-client path is JVM-independent again, so aLocalExecutorRejectionIsNotReported AsOutcomeUnknown becomes closeSessionByIdFailsFastAfterClientClose — IllegalStateException on every JDK — and the saturated/terminated taxonomy stays pinned by aTerminatedHttpExecutorReleasesTheOwnedMarker. - closeByRefTreatsAnAbsentHarnessSessionAsClosed now also counts the DELETE request (dropping the remote call reddens assertEquals(1)). - Housekeeping of the takeover branch: aRecoveryRequiredRefusalKeepsThe OwnedMarker's stub never incremented its promptCalls counter, so its trailing assertion could never pass once it ran (CI never reached it through the verify-checkout race) — fixed by counting in the handler. Witnesses: full module 214/214 green on JDK 17 (clean test green on JDK 11, 13 skips = 9 env + 4 version-gated); corridor mutation arms all red — detach with an unconditional finally flattens the keep-beating assertion, removing either close-mirror makes refused-close heartbeats keep growing (9→14, 10→16), and removing ensureOpen flips the fail-fast test from IllegalStateException back to the transport exception. * fix(sdk-java): settle registered admission markers and align lifecycle teardown Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk-java): retire hosted admission state on proven restart and scope the settle latch per attempt (#13314) A proven harness generation change now retires every local record for that peer at all three entrances: the submitTurn fence catch releases the admission ledger entry, the scheduled heartbeat retires the attachment and ledger instead of swallowing the mismatch, and detachSession mirrors closeSession for the generation branch while keeping the ledger across definitive refusals. The admission settle latch is per-attempt and outcome-scoped: only the owning call's concluded send settles it, every attempt re-arms it, and a same-identity retry's 202 publishes a fresh settled entry so a status read holding a stale snapshot cannot clear the marker that is live now. Pre-dispatch unchecked escapes release the owned marker via a dispatch boundary flag in send() instead of settle-and-keep. The SSE idle watchdog merges the parked flag and activity stamp into one volatile waitingSince write, so a tick can never pair a fresh flag with a stale stamp; keepalive refills still refresh the budget. Docs correct the persisted-digest claim: a form change invalidates rows whose stored key order was not already canonical, not every row. * fix(cli): drop ambient QWEN_HOME from the cli test setup (#13314) A run from inside a Qwen Code session always exports QWEN_HOME, which outranks the homedir() mocks the settings suite anchors on: the module-load getUserSettingsPath() then resolves to the ambient home, the mocked user-settings file is never found, and 13 settings.test.ts tests fail. The verification gate runs in exactly such an environment. Delete the variable in test-setup.ts, mirroring the existing QWEN_RUNTIME_DIR sanitization; tests that exercise QWEN_HOME set it in-body. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk-java): close hosted harness ledger wedges from review (#13314) Unfenced 2xx/3xx mutation answers are now outcome-unknown instead of definitive, unfenced refusals carry the peer's buffered body, the per-attempt settle latch is owner-gated, close/detach retire only the records they observed, and wire 409s stay reachable through DaemonHttpException. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk-java): close hosted harness review criticals on digest recovery, refusal typing, and ledger identity (#13314) - SubmitHarnessTurn accepts a persisted pre-canonicalization payload digest and re-sends the prompt in the exact as-built key order that digest covers, so recovering an older turn row reaches the route's replay path instead of failing the self-check before any request is sent. - An unfenced 4xx on the generic mutation channel surfaces as the protocol failure it is rather than DaemonHttpException, whose status consumers key terminal verdicts on; only submitTurn keeps the typed refusal its admission-marker release keys on. - ActivePrompt records the submitting client id and replaces the shared settle latch with a per-attempt send counter: lifecycle teardowns retire only markers whose identity proved stale, and a status read clears a marker only while no send attempt is in flight. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk-java): close hosted harness round-10 review criticals on close/detach retirement (#13314) - README: correct the digest-migration paragraph — the client re-sends the shape its digest covers and accepts a pre-canonicalization as-built digest, so upgrades do not quiesce or rewrite in-flight turn rows - closeSession(String): a successful headerless DELETE deleted the session outright, so retire every attachment for the id, including one registered while the DELETE was in flight - submitTurn: gate all six owning-release arms on no send attempt still open, so a definitive refusal cannot strand a same-identity retry's 202 with no ledger entry - detachLifecycle: retire the observed admission marker even when no attachment remains (a refused close drops the attachment first), in both overloads - closeSession(HarnessSessionRef): a 204 proves identity-matched deletion, so retire every marker for the id; keep identity-matched retirement only for the tolerated identity-scoped 404 * fix(cli): wait out the record watcher's backoff in the unrecorded-reply squad test (#13314) The deterministic verification gate rejected the previous commit on "wakes the leader with the error when a member reply is never recorded" (default 1s vi.waitFor timed out waiting for the leader's wake). The give-up that wakes the leader is eligible the moment the test bumps its virtual clock, but it fires on the watcher's next real-timer check, and that chain backs off geometrically (recordWatchMs doubling to a 15s cap) — under full-suite load the next tick lands past the 1s default. The sibling restart-recovery test already guards the identical pattern with timeout: 3_000 and the comment "The watcher backs off; its next check may be a while."; apply the same bound here. Assertion-preserving. Probe: with the watcher's initial interval raised to 1.5s, the unmodified test fails with the gate's exact assertion at the same line; with the timeout it passes. * fix(sdk-java): close HostedHarnessClient admission-ledger fences found in review (#13314) Round-14 review found the admission-ledger fencing still open in eight places: - detachLifecycle(HarnessSessionRef) retired the pre-send snapshot even on a tolerated 404, wiping the marker the session's current owner planted through a re-issued identity; it now retires through the same identity-matched retireLedger detachSession uses, which also covers a marker this identity replanted while the detach was in flight. - detachSession collapsed a deleting 204 and a tolerated 404 into one identity-matched retirement, so a proven deletion kept a marker from an older identity; the 204 arm now retires unconditionally. - Both closeSession overloads applied the observed-snapshot rule to a generation change, so a marker published while the DELETE was in flight survived a proven restart; both now retire unconditionally on HostedHarnessGenerationException. - The sendMutation channel and the streamEvents fence observed a generation change without retiring local state; both now discard it, and sendMutation threads the session id from every caller. - The unfenced definitive-4xx arm released without reading the refusal code; an unfenced 409 is now narrowed by code like the fenced arm, so a boot-header-stripped hosted_prompt_recovery_required retains the marker and surfaces as PromptAlreadyActiveException. - When both the owning attempt and a joined same-identity retry were definitively refused, neither released the marker; an owning release refused by an open sibling send is now owed and discharged at the last concluded attempt, while outcome-unknown or durably admitted conclusions block it. - A non-owning retry's send window closed before its 202 republish, so an owning release landing in between dropped the joined entry and the replace silently no-oped; the window now closes after the ledger decision. - The dispatch guard probed the executor, so a saturated-but-running pool was misclassified outcome-unknown; send() now dispatches with sendAsync and maps the synchronous RejectedExecutionException to a known-outcome DaemonTransportException, keeping post-dispatch rejections outcome-unknown. Each fix carries a pinning test, and the three pre-dispatch escape tests move their injection seam from the removed executor-state probe to the executor's execute(). * test(cli): settle fresh process rows before identity-judged ledger sweeps (#13314) The deterministic gate rejected the previous round on managed-runtime-ledger.test.ts: on some hosts ps reports a just-born process with the negative-elapsed wraparound (e.g. 441077234-00:18:40, which parsePsElapsed rightly rejects) until it ages past the glitch, so an unwitnessed sweep judged the live fixture 'unknown' and held it unproven instead of killing it. settledGroupTable now also waits for the leader's row to carry a datable elapsed column, and the five live-spawn sweep tests that never settled now settle before sweeping. Assertions unchanged. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk-java): report a close-aborted parked SSE read as a transport failure (#13314) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * chore(sdk-java): re-verify #13314 on a tree clean of generated Maven artifacts The deterministic gate rejected |
||
|
|
af9bd8ed05
|
feat(managed-agent): add Shell Full Access and session approval switching (#14323)
* feat(web-shell): integrate Managed session workflow * docs(managed-agent): align model-switch acceptance translations * fix(managed-agent): reconcile WebShell integration checks * fix(managed-agent): address WebShell review feedback * fix(managed-agent): align schema projections and CI probes * feat(managed-agent): add Shell Full Access and approval switching Freeze approval selection per Turn, preserve legacy retry identity, and allow owner-authorized idle policy changes with durable WebShell confirmation. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * feat(web-shell): integrate Managed session workflow * docs(managed-agent): align model-switch acceptance translations * fix(managed-agent): reconcile WebShell integration checks * fix(managed-agent): address WebShell review feedback * fix(managed-agent): align schema projections and CI probes * feat(web-shell): integrate Managed session workflow * docs(managed-agent): align model-switch acceptance translations * fix(managed-agent): reconcile WebShell integration checks * fix(managed-agent): address WebShell review feedback * fix(managed-agent): align schema projections and CI probes * fix(web-shell): scope Local approval overlays to the active backend * test(web-shell): refresh transcript size evidence after main sync * feat(web-shell): integrate Managed session workflow * docs(managed-agent): align model-switch acceptance translations * fix(managed-agent): reconcile WebShell integration checks * fix(managed-agent): address WebShell review feedback * fix(managed-agent): align schema projections and CI probes * fix(web-shell): scope Local approval overlays to the active backend * test(web-shell): refresh transcript size evidence after main sync --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
f9df3df1ef
|
feat(web-shell): integrate Managed sessions into the home workflow (#14316)
* feat(web-shell): integrate Managed session workflow * docs(managed-agent): align model-switch acceptance translations * fix(managed-agent): reconcile WebShell integration checks * fix(managed-agent): address WebShell review feedback * fix(managed-agent): align schema projections and CI probes |
||
|
|
70758572ff
|
feat(managed-agent): read-only snapshot children (#13753 I3) (#14265)
* feat(managed-agent): read-only snapshot children (#13753 I3) A hosted parent can launch a child agent with isolation "snapshot": the child reads a frozen copy of the parent's working tree, taken when it starts, and nothing it does reaches the parent's Workspace. This is the last slice of #13753, and it records as final that serialized sharing needs no value of its own: the storage lease already admits one holder at a time. The copy is a child Workspace prepared as a worktree child's is. The child bound to it gets a new hosted profile, hosted-workspace-read/1, which declares read_file and glob and nothing else, whatever lanes, orchestration or MCP the Session has. The store sets that profile from the row's mode in the transaction that checks the copy is ready, so no caller can bind a copy to a profile that writes; the tool turn refuses every undeclared call before the Broker is acquired. A snapshot's child Workspace records its mode in the request of its child Workspace operation, written once and never rewritten; a request that names none is a worktree's, so worktree rows read exactly as before and no schema change is needed. A snapshot never merges: the relay asks only its discard and holds no settlement for it, the store records a stray merge ask as a discard, and its result commits with no workspace member. A relay that meets a mode it does not serve settles the run never started instead of creating it shared. A snapshot child has no message lane, so send_message refuses it while it runs and a later message continues it as a new snapshot; a team member, which trades messages with its leader, cannot be a snapshot. A separate switch, child-snapshots-enabled (off by default, requires child Workspaces), adds childSnapshots to every hosted create and load; only a Session carrying both flags offers or admits snapshot. A replica from before this change would create a snapshot run as a shared writer, so the switch goes on only after every replica runs this version; the design gives the rollback procedure. The I1 and I2 designs gain notes, and the I2 design's decisions 5 and 8 now name the give-up of an unbound row as the one relay discard of completed work (#13905 item 1). * fix(managed-agent): resume a snapshot child's committed reads after Runtime replacement Review follow-up for #14265 (#13753 I3). - The G3 step-3 path (#14059) resumes a results_ready checkpoint without reacquiring a retired Runtime owner, but it accepted only hosted-workspace-files/1. A snapshot child's read profile fell through to acquire(), which failed with "Cleanup Runtime owner changed.", so a load driving runtime recovery returned 503. The read profile now takes the same path: its batch is always read_file and glob, the batch that path already resumes without file history. - isHostedWorkspaceSearchProfile is back to the /2 profiles only; approval pre-approves glob through the new isHostedWorkspaceGlobProfile. - HOSTED_WORKSPACE_READ_TOOLS takes read_file by name, not by position. - A team member launched as a snapshot is told only of the isolation the host serves. - The Java profile literal moves to WorkspaceToolProfiles.READ. - Docs: the I3 design and the G3 step-3 recovery note name the read profile. * fix(managed-agent): address the second review of snapshot children Pin the new Row.snapshot() accessor in the lock-order audit's exact exemption set, which main's stricter pin turned red after the merge. A running snapshot on a closing Session now gets the closing refusal, so nothing promises a continuation that admission would refuse. The read profile's read_file lookup now throws at module load when the file tools lose read_file; spreading the failed lookup would have declared a tool with no name. Add the tests the review found missing: both sides of the glob predicate, a launch body naming shared, the snapshot flag without child Workspaces, unqualified launches on a snapshot-capable host, the legal boot configuration, and foreground, folded and notified snapshot results carrying no workspace line. Update the design's file inventory, its post-merge lanes and the replaced-Runtime validation wording. * fix(managed-agent): give a snapshot child no skill catalog Main's static Skills put skill instructions into every Session whose agent definition publishes Skills. The read profile declares only read_file and glob, so a snapshot child would be told to use a skill tool it refuses, and an undeclared call fails the Turn. A read-profile Session now drops the catalog once its profile resolves, on create and on load. The design records the exclusion and that a snapshot child still inherits its agent definition's hooks. |
||
|
|
76437355af
|
fix(managed-agent): bound retry loops with terminal states (#13219)
* fix(managed-agent): bound retry loops with terminal states Asynchronous retry loops across the managed-agent stack had no terminal state, and the message projection wedged permanently on a sequence gap. Issue #13182. - Projection: a sequence gap observed under the session row lock is provably permanent, so materializeNextBatch now logs once per gap and skips ahead instead of throwing on every scan at 10Hz forever. - Turns: a new maxPostAdmissionRetries budget (default 10) terminates an admitted turn as hosted_harness_unavailable_after_admission once spent, distinct from the pre-admission code because the turn may already have run. - Operations: a new maxOperationRetries budget (default 10) completes an unsettleable close/delete unconfirmed (java_durable) after a best-effort drain, and completes an undeliverable action response FAILED with action_response_delivery_failed instead of retrying forever. A committed decision seen on the projection re-check still wins over the budget. - Scheduler: a memory-blocked pump arms a 1s recheck wake so a missed capacity notification no longer starves queued activations; transient store failures on claim/renew/release abandon only the affected activation instead of halting the worker, which still halts when the store reports consistency damage. - Store: persist() repairs a torn journal tail (truncate to the last synced byte, like the open() loader) and only fails closed when the repair itself fails. - Session close seals the writer in a finally; a failed HTTP seal keeps the grant renewed and retries the seal on the renewal cadence until sealed, bounded so a broken seal endpoint eventually lets the grant lapse for takeover instead of wedging the session. * fix(managed-agent): record honest terminal failures on exhausted retry budgets Address the PR review: the lifecycle exhaustion arm no longer reuses the success-path completion write — a new fenced failOperation terminates the operation FAILED with the settle's failure code kept, leaving the Session in its pending status (a live journal writer keeps the operation waiting instead). The gap heal settles orphaned items as failed, guarded to never touch a turn whose terminal event is in the journal or whose turn row is still live. The materializer backs off per session and surfaces a stuck projection once. The embedded scheduler retries transient renewal/release failures within the lease, halts on a consecutive-failure streak, and converges per-activation via a transient-failure budget. Pending HTTP seals now abandon after two lease durations, and a FAILED action response whose Action is still requested is re-admitted on the caller's retry. Docs, the OpenAPI descriptions, and the generated client match. * chore: retrigger CI after a dropped pull_request synchronize event * fix(managed-agent): key the writer gate on the writer, not the admitted status Round-4 audit fixes. The exhaustion gate now checks hasLiveWriter for every delivered shape — a delete of a closed Session whose retention retirement keeps refusing on a residual live writer waits instead of terminating into an unrecoverable DELETING row. The terminal arm mirrors settle()'s routing exactly: a closed-Session delete makes zero Runtime calls. A retried action answer re-admits only while the Session still accepts responses; the post-admission turn terminal keeps the Runtime Broker's own failure code; the scheduler prunes budget entries for activations that can never re-queue; and the seal bounds, blocked-fallback and fencing comments now state what the code does. * docs,test(managed-agent): align the writer-exception wording and pin the round-4 gates Round-5 audit follow-ups: the design docs' writer exception now covers every admitted status (matching the gate that keys on the writer itself, in both languages); the blocked-fallback test comment states the scanner's real re-drive condition; and the two round-4 gates that lacked witnesses gain them — a closed-Session delete terminating without any Runtime call (red when the terminal arm's short-circuit is removed) and a replayed FAILED action response staying terminal on an inactive Session. * docs(managed-agent): state the closed-Session delete's zero-call terminal routing The budget-exhausted paragraph claimed the terminal arm always releases the binding through the workspace close pair; for a delete of an already closed bound Session settle() — and now the terminal arm — deliberately makes no Runtime call at all. Both languages. * fix(managed-agent): stop the budget terminals from certifying falsehoods Round-5 bot review (R2) Critical fixes. The lifecycle terminal arm now fires only when settle() never returned — a completion-write failure after a successful settle retries instead of recording session_lifecycle_delivery_failed for a close that actually settled. The action terminal exempts the shape where the Harness answered 200 and only Java's projection lags (a dedicated DecisionNotYetProjected failure), so a committed decision is never recorded as an undelivered one. Action re-admission narrows to the delivery-budget terminal: a definitive Harness 400 keeps replaying its recorded row instead of re-sending the refused body. The scheduler's release retry re-checks liveness after its sleep, so a dispose racing the retry no longer writes a terminal release on a stopped worker. Each fix is pinned by a test verified red under its mutant. * fix(managed-runtime): keep scheduler error paths total when the store clock fails R3-1: store.getCurrentTime() validates its clock, and the renewal/release error paths called it outside any try — a throwing injected clock escaped as an unhandled rejection and leaked the active slot forever (pump skips keys in active, so the activation could neither run nor re-queue, and the worker silently stopped claiming once every slot leaked). Read the clock through a helper that treats a throw as no-headroom/lease-expired, move the slot cleanup and trailing pump into execute()'s finally, and turn any escaping execute/renewal rejection into a loud halt. Pinned by a broken-clock test verified red (timeout + unhandled rejection) against the pre-fix source. * test(managed-agent): give bound Session fixtures the tool profile main added Merging main brought #13301, which adds a toolProfile component to SessionRecord and turns a null profile on a bound Session into an invalid state: QwenHostedHarnessConnector throws "Hosted Workspace Session tool profile is missing". The bound fixtures in the two retry-terminal suites still used the 16-argument shape, so the module stopped compiling after the merge. The value is inert for these tests - both suites mock HarnessConnector, and toolProfile() is read only inside the real connector - but a bound fixture without a profile now describes a state production rejects, so it follows main's own HarnessCoordinatorTest fixtures. * fix(managed-agent): keep the named load refusal code past Turn admission The post-admission exhaustion arm this branch adds recorded the generic hosted_harness_unavailable_after_admission for every error except a Runtime Broker failure, so a named load refusal (#13349) reaching that arm lost its own code and blamed Harness availability for a Session the Harness had deliberately refused to open. The arm is reachable with a refusal because submissionAttempted is seeded from the claim: a Turn an earlier dispatch already submitted, re-driven while a mixed-version fleet refuses its load, exhausts this budget with a refusal in hand. Within a single dispatch the load precedes the submission marker, so the pre-admission arm keeps handling the common case. Mirrors the pre-admission arm's precedence and logs the refusal code through failureLabel rather than the exception class name. Mutant-verified: deleting the new branch turns namedLoadRefusalsHonourThePostAdmissionBudget red, recording hosted_harness_unavailable_after_admission where managed_session_open_failed is wanted. * fix(managed-agent): close round-4 review findings in the terminal-outcome paths Consolidate the embedded scheduler's terminal-outcome handling into execute(): every abandon path now converges after the handler settles, where the scheduler records the handler's own outcome with the budget spent — re-claiming a lapsed lease so the write fences live, yielding one commit turn so a racing dispose/halt cancels it, and guarding the failure classification so a stopped worker never latches a bogus halt. Stale-ended runs now count the dropped run against the activation budget, bounding re-runs of an activation whose store calls keep resolving past the lease edge, and a terminally recorded activation still reports its handler error through onActivationError. The activation journal repair now observes the file length before truncating: truncate() extends a shorter file with NULs and reports success, so a journal that lost synced bytes out of band must classify fatal, not transient. On the Java side, the action-response re-admission gate no longer resurrects a FAILED delivery row whose Action has expired (a fresh admission would refuse 409 action_expired, and the resurrected PENDING row wedged every later lifecycle command via requireNoOpenOperation), and HostedHarnessClient.submitTurn probes GET /session/:id/status once before refusing a different pinned prompt, so an outcome-unknown admission entry left behind by a terminal failure stops locking the Session forever (from the maintainer's stack-validated candidate patch). * fix(managed-agent): close round-5 review findings in the terminal-outcome paths The embedded scheduler's spent-budget terminal record could not tell "the handler settled and we could not release it" from "we aborted the handler ourselves": a cooperative handler returns normally on abort, so a scheduler-induced cancellation was durably written as outcome 'completed' — a final, non-reclaimable false success. Track whether the abandon preempted the handler and record 'failed' for that shape; a run that genuinely settled before the abandon keeps the handler's own outcome. ActionResponseCoordinator's budget terminal inferred "the Harness never answered" from the exception type, so a Java-side store fault after resolveAction's 200 — the decision is already committed then — was recorded as action_response_delivery_failed with harness_confirmed false. Track the answer fact (200 return or the definitive 400) and gate the terminal on it instead of the type. HarnessCoordinator's post-admission budget terminal failed a Turn the Hosted Harness may still be executing, and failTurn clears the dispatch owner, so nothing could later cancel the admitted Turn or re-attach to journal its output. Reconcile before terminating: best-effort cancel through the Session's bound Harness, then record the failure. The action-response re-admission gate healed a budget-terminal action_response_delivery_failed row only while the Action was still requested; once the projection showed the Action decided with this very response's decision — the delivery did land and only Java's record was wrong — the replay returned the stale failure forever. On a digest match the replay now settles the row in place (COMPLETED, harness-confirmed, with the decision receipt), fenced on the terminal FAILED state; every other end state keeps replaying the recorded failure, and the row is never resurrected to PENDING, so a second vote stays impossible. * fix(managed-agent): close round-6 review findings in the terminal-outcome paths * chore(managed-agent): republish round-6 fixes after the lint-gate rejection The deterministic gate rejected the round-6 commit at npm run lint: eslint scanned untracked maven javadoc output (packages/sdk-java/*/ target/reports/apidocs/*.js, plus runtime-broker test-classes *.mjs) left behind by the local verification runs. That output is git-ignored build product, not part of the fix, and the only committed-tree lever (the workspace-root eslint config) is verification machinery outside this PR's footprint. The build output is removed and the round-6 commit is preserved unchanged; this empty follow-up re-publishes it with a workspace that passes build, typecheck, and lint. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(managed-agent): close round-7 review findings in the terminal-outcome paths - SubmitTurn's busy-turn status probe no longer leaks its own DaemonHttpException/DaemonProtocolException types: a failed probe keeps the plain retryable busy-turn refusal instead of terminal-failing a Turn whose prompt was never submitted (R7-2). - The action-response answered fact is durable in the row's budget-exempt watermark: answered attempts reschedule budget-exempt and the terminal gate reads the watermark, so a mid-projection-lag failure after an answered attempt never records action_response_delivery_failed; the re-admission UPDATE resets the watermark so a re-clicked row keeps a terminable budget (R7-3). - A delete admitted on an ACTIVE Session now publishes its live-writer wait as recovery_blocked/session_close_writer_live once the budget is spent, and the recovery scan re-drives every blocked lifecycle shape, matching the published design contract (R6-2). - Reformat the two durable-lifecycle design-doc tables with Prettier so the lint lane passes (R7-1). * chore(managed-agent): republish round-7 fixes after the lint-gate rejection The deterministic gate rejected the round-7 commit at npm run lint: eslint scanned untracked maven report output (packages/sdk-java/*/ target/site/jacoco/jacoco-resources/*.js) left behind by the local verification runs. That output is git-ignored build product, not part of the fix, and the only committed-tree lever (the workspace-root eslint config) is verification machinery outside this PR's footprint. The build output is removed and the round-7 commit is preserved unchanged; this empty follow-up re-publishes it with a workspace that passes build, typecheck, and lint. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(managed-agent): renumber the budget-exempt migration to V46 past main's V41 Main's #13289 landed V41__workspace_csi_reservation.sql while this branch carried V41__managed_operation_budget_exempt.sql, so the merge made two migrations claim version 41. Flyway fails migrate() on duplicate versions, which took down the Flyway uniqueness gate and both DB-backed jobs that boot the server (MariaDB integration and hosted fault gates). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(managed-agent): pin cwd probe retries against the lifecycle terminal budget Follow-up to the main merge: widen the two OperationRecord construction sites in OperationRetryTerminalStateTest to the merged 18-component shape (the merge commit's staged copies predated this fix), and add coordinatorNeverAppliesTheLifecycleTerminalBudgetToACwdChange — with the lifecycle budget configured below the probe budget, a transiently failing cwd probe keeps its plain reschedule (RUNNING/PENDING, zero budget-exempt watermark, untouched binding) instead of taking the close/delete terminal arm or the writer-live wait. Mutation probes: deleting the terminal-arm gate turns the pin red with state FAILED; deleting the writer-live gate turns it red with RECOVERY_BLOCKED. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(managed-agent): renumber the budget-exempt migration to V48 Merging main brought #13497, which took V47 for managed_channel_route_delivery. This branch's budget_exempt_attempt migration was also V47, so Flyway refused to resolve the schema at all: Found more than one migration with version 47 -> V47__managed_channel_route_delivery.sql -> V47__managed_operation_budget_exempt.sql Every Spring-context test then errored — 740 of 1112 locally — which reads as a catastrophic regression but is one duplicated version number. The column is this branch's own: main ships no budget_exempt migration and its Java has zero references to budget_exempt_attempt, so V48 is simply the next free version and nothing depends on the old number (no test pins a migration count or version, and the column is referenced by name only). scripts/check-flyway-migrations.js now reports 48 migrations, all versions unique. * fix(managed-agent): stop renewing a failed close-time seal; bound the writer wait by retryability (#13182) R1-38: the pending-seal cadence kept a failed-seal writer's grant live, and the backend replays the same writer generation to a same-credential re-acquire during a live lease, so the stale client could seal the journal out from under a freshly re-attached session. Drop the cadence: the grant lapses, a post-lapse re-acquire bumps the generation and fences the stale client, and a later close() still retries the seal (accepted without a live lease). R1-30: the live-writer wait exempted every settle failure from the terminal budget, so a permanent refusal thrown before the Harness was asked to stop (e.g. workspace_close_identity_unverified) re-blocked the operation forever while the Harness kept the writer. The wait now keys on the failure being retryable, so a permanent refusal terminates with its code even while the writer is live. * fix(managed-agent): publish the stale-Harness wait instead of hiding it The generation-mismatch wait was budget-exempt and invisible. The terminal arm is vetoed by !staleBoot, and the exempt reschedule writes budget_exempt_attempt = attempt_count, so the effective budget stayed at zero and the row read RUNNING/PENDING with error_code NULL — byte-identical to a healthy retry — for as long as this replica kept its stale Harness boot, which only a Java restart clears. The writer wait, the other unbounded external wait, already publishes recovery_blocked with its code past the budget; there was no reason for this one to be the invisible half, and an operator cannot restart the replica that a row does not name. Publishes it the same way, as hosted_harness_generation_mismatch. The wait stays unbounded and the recovery scan still re-drives it, so nothing terminates earlier than before — the row is only legible now. Gated on !cwdChange because a cwd change never reaches settle() and is bounded by its own CWD_CHANGE_ATTEMPT_BUDGET. Contract and docs follow: the failure_code description on both OpenAPI surfaces and the regenerated client, and the durable-lifecycle design doc in both languages, which also claimed "the one exception" and then described a second one four lines later. Pinned by aStaleHarnessViewPublishesItsWaitPastTheBudget (renamed from ...KeepsTheOperationWaitingPastTheBudget, which asserted the invisible retry) plus aStaleHarnessViewReschedulesExemptlyBelowTheBudget, so publication stays budget-gated rather than becoming the first response to a generation error. Mutant-verified: deleting the new branch turns the first test red. * fix(managed-agent): bound the answered action response by its Action's expiry Round 7 made "the Harness already answered" a row-level watermark: once budget_exempt_attempt is set, `answered` is true on every later attempt, so the terminal gate `!answered && ...` became unreachable and the only reset was a client replay. Java keeps no expiry scanner for managed_agent_action — only the journal projection writes it — so an answer whose decision never projected retried forever. That is not merely a spin: hasOpenOperation counts ACTION_RESPONSE rows and requireNoOpenOperation gates close, archive, delete, rename and unarchive, so one such row refuses every later lifecycle operation on its Session with 409 session_operation_active while reading as a healthy RUNNING/PENDING with a NULL error_code — the wedge this PR exists to remove, reintroduced. Bounds the exemption by the Action's own life, which is the condition the exemption exists for: waiting only helps while the Action can still be decided. Admission validates expiresAt as a required number greater than createdAt, so a real Action always carries that deadline; once it has passed while the Action is still `requested`, the operation completes FAILED with `action_response_decision_expired`. The code is its own rather than action_response_delivery_failed because the Harness did answer, and the record stays java_durable because with no projection Java cannot certify what was committed. An absent expiry is not read as a deadline, so a row this code should not judge keeps waiting instead of terminating on an invented one. Contract and docs follow: the failure_code description on both OpenAPI surfaces and the regenerated client, and the actions design doc in both languages — which also still described re-admission as needing only `requested` plus a Session that accepts responses, omitting the expiry condition the code has checked since round 4. Pinned by anAnsweredResponseTerminatesOnceItsActionExpired. Round 7's aLaterFailureAfterAnAnsweredAttemptStaysBudgetExempt stays green unchanged: its Action carries no expiry, so the exemption it pins still applies. Mutant-verified: removing the bound leaves the new test red with "wanted but not invoked", i.e. the row retries forever. * chore: retrigger CI after two dropped pull_request synchronize events The pushes of |
||
|
|
b5a266e1ed
|
feat(managed-agent): wire static Skills and extension configuration (#14319)
Connect published Skill resources and administrator-provisioned MCP/Hook pins to immutable Managed Agent sessions, with on-demand loading and durable recovery. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
e5f8aaaf35
|
fix(cli): close post-merge takeover findings from the #13083 review (#13188)
* fix(cli): close post-merge takeover findings from the #13083 review - Track the recovered-but-undriven Turn on the Session: /status reports it in flight and /prompt refuses a fresh Turn until continue/cancel settles it, instead of looking idle over an unsettled checkpoint - Map reconciliation failures in the recovered continuation to recovery-blocked instead of a terminal turn_result, so the coordinator's next takeover load can re-drive the Turn - Read a 409 runtime_broker_execution_unknown as a definitive not-found only when the Broker marks it terminal, so a reconcilable UNKNOWN no longer skips the cancel of a possibly running execution Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): deflake the lost-cancel replay witness The test could meet the second cancel before the first one registered its admission: the route awaits the run authorization between request and admission, and polling /status no longer proves the admission once a takeover also reports the Session in flight. Gate the replay on the stop step's first broker status read, which only runs after the route registered the admission, and arm the first cancel explicitly — the PROBE .then() it replaced was load-bearing, since supertest requests are lazy and only fire from a continuation. * fix(cli): handle the /review round on takeover follow-ups - broker: return the terminally abandoned record as its own 'abandoned' state instead of folding it into the not-found sentinel the stop step reads as already stopped; share one terminal-unknown classifier with the observation poll (review R1-1) - recovery: accept 'abandoned' as cancellation-complete for the cancel and terminal poll while a reconcilable unknown keeps failing closed, and say in the contract comment that this stop is unproven - session: keep a pre-wrapped recovery error un-wrapped so the block log names the cause an operator needs (review R1-3) - session: refuse a user cancel in the undriven-takeover window with a scoped 409 instead of confirming a cancellation that never happened, and log the fresh-prompt refusal for oncall (review R1-2) - tests: a raw 'warm gone' escape pins the continuation wrap that the retried workspace refusal fixture could not (review R1-4); add the abandoned stop-step witness (review R1-5) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): handle the /review round on takeover follow-ups (round 2) - A blocked-for-recovery escape does not exist on the continue route — the Session stays registered and the connector drops its recovery tracking on the 200 admission — so a raw reconciliation escape (a transient warmup failure or a retryable re-acquire refusal) settles the Turn terminally; only an already-wrapped recovery error keeps the recovery-blocked handler (review R2-1) - Record a cancel replay certified while the stop still ran, and stop waiting for a retry that cannot arrive in that case: the failure now reports the Session recovery-blocked (marker cleared) instead of armed-and-idle, while the plain 503 path keeps dropping its admission for the retries that do come (review R2-2) - A refused lease release after a confirmed stop no longer 503s over a settled cancellation: the route settles the Turn, owes the lease for the next terminal route to retry, instead of trapping the coordinator in an identity-mismatch loop (review R2-3) - recovery: one stop-terminal predicate shared by the pre-cancel skip and the post-cancel poll, plus the abandoned-mid-poll witness; the passive report fold gains its abandoned parameter (reviews R2-4/R2-5) * fix(cli): handle the /review round on takeover follow-ups (round 3) - Arm the undriven recovered marker only for a recovery the coordinator can actually drive: a passive report with any unknown execution outcome (and a drive report short of results_ready) is answered without the marker, so the Session stays usable instead of refusing every later route forever (review R3-2) - A Turn settling without completing now drops its pending file-history obligation with it — one transient continuation failure no longer wedges every later cold load behind hosted_turn_recovery_required (review R3-3); the witness gains the detach-plus-cold-load leg it claimed - The owed-lease marker records the identity whose release actually failed (the stop step's journalled runtime session id), so the MCP profile's drifted promptId-vs-runtime id cannot pin a tool session that was never acquired, and retried handbacks land on one owner (review R3-1, half 1) - Give an owed lease a retry site that actually runs: a settled /prompt settles its finally by retrying the owed handback, and the replay's terminal route keeps its retry too; the owed-lease witness now drives the counts through all three sites on a persistent refusal and pins every attempt to the same owner (reviews R3-1 half 2, R3-4) - Assert the two operator-visible stderr lines in the tests that reach them (review R3-5) * fix(cli): handle the /review round on takeover follow-ups (round 4) - Retire the pending file-history marker safely: one shared dropPendingHostedFileHistoryTurn in hosted-file-history.ts (used by both the cancel and continue routes), called only when the settled Turn can no longer resume and only after the terminal record landed; a failing retirement no longer turns a completed continuation into an error, and it stays owed — retried by the next settled /prompt or by close — instead of wedging every later cold load behind a refusal (review R4-1) - A cancel whose fallible step fails after the durable checkpoint moved (the file-history retirement or the terminal record write) can never be re-driven against the same recovery identity, so the route admits it loudly — best-effort terminal record, recovery-blocked, marker cleared — instead of staying armed-and-idle with a permanent identity-mismatch for the coordinator (review R4-2) - The undrivable takeover witness now observes the admitted Turn's real terminal outcome before teardown instead of racing its own cleanup into an ENOENT (review R4-3) - The load's final else arm logs the reconciliation root cause before answering managed_session_open_failed, so a refused load-resume is diagnosable without losing the retryable arms' raw mapping (review R4-4) - A recovered Runtime Session the Broker already forgot is treated as handed back: one releaseIsSatisfied predicate clears runtimeLeaseHeld on 404 in both shared release helpers and the inline release, so an owed handback retires instead of retrying with log noise forever (review R4-5) - The user-cancel guard refuses the blocked-and-undriven state too, so a replay-certified cancel failure cannot fall back to a 204 that certifies a cancellation nothing stopped (review R4-6) - /files/rewind gains the undriven-marker guard, so a route that borrows session.active as its busy flag cannot be admitted over a recovered Turn it would wedge (review R4-7) * fix(cli): handle the /review round on takeover follow-ups (round 5) - Keep the fence of a terminally abandoned record in the passive recovery report instead of folding it away as unknown-without-marker: an execution the Broker fenced permanently arrives as known + state 'abandoned', which lets the coordinator cancel it via stopComplete, while a still-reconcilable unknown keeps advertising unknown. The drivable decision now lives in recoverHostedRuntimeTurn itself (every-known, with drive restricting to results_ready) instead of being re-derived at the load site (review R5-3) - Retirement after a terminal settle now runs everywhere a Turn is durably settled: the continue body's outer catch retires the pending file-history marker next to its compensating write, and every owed retirement records its identity before handing itself to /prompt or close; on the cancel route the same flag guards a terminal outcome that was already durable when any later step failed (reviews R4-1, R5-2) - The cancel catch orders settledDurable before the replay arm: a checkpoint that already moved finishes its idempotent settle first (the journaled set journals nothing twice), and only a settle that cannot finish again leaves the Session recovery-blocked, because a completed terminal record serves the admission's watermark better than a permanently blocked Session (review R4-2) - The settledDurable flag derives from the checkpoint identity, not from the settle call's return, so a partially applied settle inside one resolveAwaitRuntime at a time is told apart from a retryable miss; the never-completed witness pins recovery-unravelling into recovery-blocked with a no-turn_result guard for the still-unjournaled functionResponses (review R5-1) - On merge of main: refuse-load ownership recording (refusedAdoptions), release-after-terminal-record without redoing answered cancellations, the load-refusal log phrase kept for the pinned variant, with the cause-chain root intact for wrapped ones * fix(cli): close the round-6 takeover review's lease, marker, and honesty gaps - gate the settled-/prompt lease release on a terminal route's failed handback so a passively adopted lease is never compensation-released - retire an owed file-history marker at /prompt admission, awaited inside the busy flag, and retire a settled Turn's durable marker at load - keep a replayed-but-failed cancel re-drivable instead of latching a permanent recovery block, and owe the marker only for a durably settled Turn - answer the user cancel 204 on a recovery-blocked Session whose Turn already settled; only an armed undriven recovery refuses - journal an unobservable outcome for executions accepted on the Broker's terminal fence instead of certifying an unwitnessed cancellation - teach the wire type and the Java client the abandoned recovery state Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): close the round-7 takeover review's cancel failure-semantics gaps - classify a settle failure against the checkpoint the drive started from, never the request's possibly-stale load-time checkpointId, so a transient miss that moved nothing durable stays re-drivable instead of latching a permanent recovery block - surface a refused compensating terminal write and drop the admission, so the coordinator's retry re-drives the idempotent settle plus the record instead of replaying a watermark no turn_complete can confirm - owe the file-history marker only once the terminal record landed, and keep the recovered-Turn refusal armed until it does Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): close the takeover review's cancel/continue re-drive gaps - A partially applied settle that cannot finish now drops the cancel admission instead of latching the Session recovery-blocked, so the coordinator's retry re-runs the idempotent settle and a healed store settles the Turn instead of wedging every later cancel on a 409. - A terminal cancel write that committed durably but lost its acknowledgement is detected by re-reading the durable log: the settled Turn's marker is owed and disarmed, so the settled replay answers the retried cancel and a fresh prompt is admitted instead of the marker wedging the Session forever on a record that exists. - A redriven takeover load re-arms the undriven recovered-turn marker when the re-answered recovery is drivable, matching the first load, so a fresh prompt can no longer race the parked executions it describes. - A retryable Workspace acquisition refusal escaping a continuation's resumeCommittedResults() raw is classified into the recovery-blocked handler, so the recovered Turn stays re-drivable by a later takeover instead of settling terminally and stranding the checkpoint's unconsumed Runtime receipts. * fix(cli): keep the adopted Runtime lease on recovery-blocked continue exits A continue drive classified recovery-blocked still ran the route's unconditional finally handback, and the blocked replay refusal released too: a successful release persists RELEASED, so every later takeover's same-identity re-acquire conflicts with runtime_session_not_acquirable and the parked Turn can never be re-driven. Both exits now keep the lease held, mirroring the cancel route's blocked-refusal discipline; close remains the single handback owner. Also pin the witnesses this round's review found missing: - release-spy assertion on the re-drivable continuation test - compensating-settle marker retirement covered for both the direct drop and the owed-then-retired failure half - dropPendingHostedFileHistoryTurn fixture gains pendingMessageId and undoReceipts so the rebuild shape and the merge are pinned - the late-rejected handback's owed arm is epoch-guarded by a mirrored re-adoption test - the Monitor wake pump test gains its precondition, retry-refusal and delivery oracles - gate the acknowledgement-lost fault so the fresh-prompt phase witnesses a clean settle Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): close the takeover review's redrive, wake-pump, and settle-honesty gaps Round-4 review of the takeover stack, six Criticals: - A redriven load that settles the parked Turn (the cancellation settle, the inapplicable projection, and the no-parked-Turn restatement) never discharged the undriven marker an earlier load's lost reply had armed, so /prompt refused the Session for the rest of the incarnation. All three arms now disarm once the Turn is settled. - The Monitor wake pump parked on the marker alone, so an undrivable redrive released it onto the parked checkpoint while the journal still held the input unsettled; the pump now reads the same unsettled-inputs gate /prompt refuses on. - settleParkedTurnCancelled resolved void on three silent exits, so a cancelled Turn whose settle could not even verify the park was certified settled. It now reports settled | noop | not-runnable; the cancel route treats not-runnable as a retry-inviting failure and the compensating retry maps the real outcome. - The user-cancel marker guard duplicated the unsettled-input refusal five lines below it and refused the settled-Turn case that must keep its 204; the journal guard now owns the refusal alone. - A recovery-blocked /prompt exit still retried the owed lease release, persisting RELEASED under an identity the next takeover must re-acquire; the retry now mirrors the continue route's blocked discipline. - A Broker record that vanished after its cancel was issued counted as a witnessed stop; it now joins the unobserved set. * fix(cli): close the takeover review's handback, settle-honesty, and noop-disarm gaps Round-9 review of the takeover stack, four Criticals: - releaseIsSatisfied treated any 404 from release() as a completed handback, so a foreign-coded 404 (a gateway, route version skew, a Broker-side route miss) cleared the lease flags and pinned the Workspace lease forever. The predicate now requires the runtime_session_not_found code, matching hosted-hook-session and the Broker's own 404 gate. - The cancelled settle recomputed its outcome from each attempt's Broker reads, so a compensating retry republished a witnessed cancellation over a durable journal entry that honestly reported the outcome unobserved. The dedup projection now keeps the journaled parts keyed by functionCallId and the retry republishes them verbatim. - The unobserved set died with its request: a record reclaimed after a failed attempt read as already-stopped on the retry and was certified as a witnessed stop. The cancel drive now accumulates the ids on the Session through a caller-owned set the stop fills as it counts — a stop that throws mid-way still carries them — and clears the entry when the Turn's terminal record lands. - The cancel drive's nothing-durable branch deleted the admission without checking whether the refused terminal write had actually committed, wedging the Session on a committed turn.settled with the recovery marker armed forever. It now mirrors the sibling branch's settledRecordLanded scan: disarm and keep the admission when the record exists, delete only when nothing committed. Also fixes the Lint & Static failure: parkToolTurn's `boolean | typeof CALL[]` parameter type violated @typescript-eslint/array-type and Prettier; it is now `boolean | Array<typeof CALL>`. * fix(cli): deflake the owed file-history admission witness The admission-retirement witness synchronized on the owed drop having been invoked, but the continue drive's busy flag clears only in its finally, so the follow-up /prompt could meet a 409 hosted_turn_active while the daemon behaved correctly — a ~2% red that reads as a product regression. Gate the prompt on hasActivePrompt clearing, the same synchronization the sibling settle witnesses already use. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): honor the unobserved-stop contract on every interrupted-turn settle exit (#13188) Review round 9 found three holes in the interrupted-turn runtime settlement: - R9-1: a broker status() rejection at the pre-cancel read or the post-cancel poll unwound past every unobserved.add site, so the execution id never reached the caller's carry; a retry then read the reclaimed record as already-stopped and certified a cancellation nobody witnessed. Both reads now count the id into the carry before the fault unwinds. - R9-2: settleInterruptedTurnRuntime discarded settleParkedTurnCancelled's 'not-runnable' verdict, letting the tail and the caller's terminal record build on a settle that journaled nothing. It now throws a plain retry-inviting Error, matching the takeover cancel drive. - R9-3: the abandoned-call tail never received the unobserved set and stamped executionStatus 'cancelled' on calls whose stop was never observed. It now answers them with the honest unobservable outcome, exactly as the parked-execution settle does. Also fixes two stale stopParkedRuntimeExecutions call sites left over from the {broker, unobserved} return-shape change: one in settleCrashedWakeTurnAftermath (a TS2740 that broke build/typecheck) and one in its test (which made the broker identity assertions fail). * test(cli): deflake the three serve tests the verification gate caught The merged tree's gate run failed six tests in five files; the three whose details survived the capture all share one shape — a timing budget sized for an idle runner: - hosted-harness-session: both child-acceptance tests witnessed turn.settled in the journal and then asserted a 204 close single-shot, but the pump's busy epoch is still closing out at that point and the close can answer 409 for one more tick (the window the F10 test documents). Both closes now retry through vi.waitFor, exactly like F10. - hosted-hook-session: the 30-fire drain history costs ~25 s unloaded here and overran the gate's 60 s test timeout under runner contention; pin a 180 s per-test budget. - acp-http/transport: the SSE helpers' 2 s default frame budget aborts a healthy stream when a contended runner stalls the event loop for seconds; raise the default to 15 s. Call sites that assert silence keep their explicit budgets. * fix(cli): carry unobserved runtime stops through the wake aftermath settle (#13188) - settleCrashedWakeTurnAftermath discarded the stop's unobserved set, so a Broker-fenced execution journaled as a witnessed cancellation (R10-1). - The pre-cancel 'unknown' throw never counted the id into the caller's carry, so the retry certified a stop nobody observed (R10-2). - The carry was add-only: an id counted by a transient fault survived a later attempt's positive settled witness and journaled a fence that never happened. Prune at the two positive witnesses only (R10-3). * test(cli): stop over-asking frames the stream never carries (#13188) The raised 15 s default frame budget turned two latent over-asks into test-timeout hangs: both call sites waited for more frames than the server ever sends, previously truncated silently by the 2 s budget. - TOCTOU test: the session/new reply rode the delivery stream newSession opened, so the later connection stream is owed only the load reject (a replayed duplicate may or may not precede it) — read until it lands. - Untrusted-workspace Workflow test: the connection stream carries exactly five replies, not seven. * test(cli): drop a leftover DIAG console.error from a serve test (#13188) * fix(cli): honor the settle verdict and unobserved carry in the wake aftermath (#13188) - R11-1: settleCrashedWakeTurnAftermath discarded settleParkedTurnCancelled's 'not-runnable' verdict, so a settle that could not verify the park still released the lease, consumed the input and journaled the Turn's terminal record. It now throws into the function catch, answering 'pending' for the retry — the contract the three sibling call sites already honor. - R10-1: the aftermath's unobserved carry died with a thrown stop (a per-pass local), so the pump's retry read the reclaimed record as already-stopped and certified a cancellation nobody witnessed. The carry now seeds from and persists to session.recoveryUnobserved, clearing when the Turn settles — mirroring the cancel drive. - R11-2: pin the abandoned-call tail's per-item discrimination with a mixed-batch witness (one fenced execution, one witnessed stop), the shape under which a batch-level flattening previously survived every shipped test. * fix(cli): persist pruned unobserved carries and refold committed siblings on the compensating cancel settle (#13188) * fix(cli): refold committed siblings on the continue route's settle exits (#13188) R13-1: the continue route's compensating settle wrote the terminal record without the committed-sibling fold the cancel route just gained, so a takeover continue drive faulting before its resume (the drive's first project() read) durably settled the Turn with a committed team_create/task_create still unanswered — and the kept admission replays a retried continue without re-driving, so no route ever paired it. Core's orphan repair would then tell the next Turn's model to retry the call, redoing committed work under a new key. Both continue settle exits now run answerCommittedTurnCalls (the uncommitted: 'none' variant, per the hosted-runtime-recovery contract) ahead of the terminal record: the compensating arm in the outer catch, and the direct error settle after the inner catch. Each fold gets its own try/catch, so a fold refusal is never mistaken for an ambiguous terminal-write failure — the Turn stays re-drivable instead of settling over unanswered calls. Pins (mutation-verified: deleting either fold turns its test red): - the compensating settle answers the orphaned team_create when the drive's first project() read fails - the direct settle answers it when the resume fold's own answer write fails raw Also declare the teams flag on the domainEnablement fixture: the team fold tests set it, and without the declaration the file no longer compiled (TS2339), which broke the build and every downstream CI job. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): deflake the park settle waits the verification gate caught (#13188) The merged tree's gate run failed five tests in hosted-harness-session, all at one shape: a vi.waitFor on the 1 s default budget polling a full HTTP settle (202 prompt -> refused durable write -> status flip). Solo the park completes in ~400 ms; under the gate's 25%-worker contention the settle outlives 1 s and the wait times out with the last poll's hasActivePrompt still true. The waits are healthy; their budgets were sized for an idle runner. Pin the file's established 15 s / 100 ms budget on the failing class: the shared parkUnsettledPlainTurn helper (feeds all five failures), the three waits in the compensating-settle test body, and the byte-identical park in the cancel-on-settled test. No assertion changed or removed. * test(serve): widen approval-fallback and issue-13328 windows for CI contention (#13188) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): answer committed board commands on every recovered-Turn settle exit (#13188) R13-1: the three committed-sibling folds this PR added to the continue route (direct and compensating settle) and the cancel route's compensating settle copied the fold's argument list without `commands: session.commands`. Without the command funnel the fold cannot see a member's committed board call (`commandId` stays undefined), and `uncommitted: 'none'` then skips it, so the terminal record lands over an unpaired call that the kept admission never re-drives. All four folds on these two routes now go through one helper, answerRecoveredTurnCommittedCalls, so they share a single argument list. Witnesses (the member command-wait park, continued after a takeover): - the compensating settle after the drive's first project() read fails - the direct settle after resumeCommandWaitRuns fails raw Both assert call-1 gets exactly one answer, taken from its command journal. Mutations: dropping `commands` from the helper turns both red (`expected [] to have a length of 1`); restoring the commands-less inline list at either continue exit turns only that exit's witness red. The cancel route's compensating exit is reachable only from a Runtime park; its existing team_create witness covers the fold there, and the helper gives it the same argument list. * fix(cli): retire the owed file-history marker before recovered-lease cleanup (#13188) A cancellation whose marker retirement fails once lands turn.settled on its compensating path and owes the marker in memory. The recovered lease's hosted.cleanup obligation refuses to release while the marker still names its Turn, but both terminal exits ran that cleanup first: - close released the lease, swallowed the refusal, then retired the marker and removed the Session, so it answered 204 with Broker.release never called; - the lifecycle effects phase retried the owed cleanup with nothing left to retire the marker, so it answered hosted_lifecycle_recovery_required on every retry. This is the control plane's close path for a bound files Session. Both exits now go through retireOwedFileHistory before any lease cleanup: releaseLeaseNow (close/detach and the v2 settle_resources phase) and the lifecycle route, which holds the closing gate before it. A failed retirement still stays owed in memory and durably, and the next retry or load retires it. Witness: an it.each over DELETE and lifecycle effects + detach, with warm() returning the Runtime binding so the parked owner commits a real cleanup obligation, asserts Broker.release reaches the original Runtime once and the obligation becomes confirmed. Dropping either retirement site, or restoring close's release-then-retire order, turns its case red. --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code[bot] <qwen-code[bot]@users.noreply.github.com> Co-authored-by: qwen-code-autofix <qwen-code-autofix@users.noreply.github.com> Co-authored-by: qwen-code-autofix <qwen-code-autofix@localhost> |
||
|
|
859a337500
|
chore: adopt qwen-code-checks.xml and fix all checkstyle violations (#14186)
* chore: adopt qwen-code-checks.xml and fix all checkstyle violations
Add qwen-code-checks.xml (based on alibaba/fastjson2 convention) with
ImportOrder enabled (groups *,javax,java; static at bottom). Enable
includeTestSourceDirectory so both main and test sources are checked.
Fix all 869 violations across src/main/java and src/test/java:
- Reorder imports to *,javax,java groups with static-at-bottom
- Remove unused imports
- Normalize control statements (if/for/while/do/else/try-catch-finally)
to multiline braced form
- Fix indentation to match expected levels
- Split multiple statements per line
- Fix whitespace around braces, annotation style, and DOT spacing
mvn checkstyle:check -> BUILD SUCCESS, 0 violations
mvn test-compile -> EXIT 0
* fix(sdk-java): repair the checkstyle rewrite and satisfy qwen-code-checks.xml
Two mechanical passes in the original rewrite were wrong, and neither was
caught because the verification in the description never actually ran.
The control-flow pass inserted braces around wrapped expression
continuations, treating a ternary, a cast, a string concatenation or a
constructor call in a ternary branch as a braceless if/for body. That left
the module unparseable: javac reports 809 syntax errors across the module,
while the same sources on the merge base parse clean.
The import pass was not anchored at column 0, so it reached inside a Java
text block, treated the embedded TypeScript import as an unused Java
import, and deleted the class declaration, a field and a live ObjectMapper
import along with it.
Each spurious brace is undone only when doing so strictly lowers javac's
syntax error count for that file, which keeps the insertions NeedBraces
genuinely requires; nested constructs converge one level per pass. The
text-block file is rebuilt from the merge base and reordered by a
column-0-anchored pass that refuses to rewrite a block holding anything it
does not recognise.
The remaining violations are then cleared: imports into the *,javax,java
groups, whitespace from checkstyle's own line:col so no edit can land in a
literal or comment, and the two places where the ruleset has no mechanical
solution -- an empty switch-arrow block, where RegexpMultiline forbids
"-> { }" and WhitespaceAround forbids "-> {}", so the no-op default arm is
dropped; and a switch expression directly after "(", where ParenPad and
WhitespaceAround contradict, so it moves into a local.
checkstyle:check goes from unparseable to 0 violations, against 1835 on the
merge base under this ruleset. javac is clean, test-compile succeeds, and a
token-level audit against the merge base -- literals and comments compared
verbatim, braces and whitespace ignored -- shows no difference outside the
six edits this message names.
* ci(sdk-java): gate managed-agent-server checkstyle in the fast lane
qwen-code-checks.xml applies only to managed-agent-server, but nothing
enforced it outside the clean-verify lanes, which need MariaDB and a host
lock and have starved before. checkstyle:check declares no dependency
resolution, so it needs no sibling install and slots in beside the two
existing checkstyle steps.
64669 bytes, within the 4096-byte ratchet over the 69416 baseline.
---------
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
|
||
|
|
462cae3dd8
|
fix(managed-agent): Wire OSS capture budgets into Hosted sessions (#14124)
* fix(managed-agent): Wire OSS capture budgets into Hosted sessions Pass the configured raw output budget on enabled Shell-family creation while preserving saved budgets on load. Cover the SDK wire shape, connector profile selection and the public lifecycle collection path. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(managed-agent): Align Hosted recovery fixtures with OSS capture Adapt the public and process-recovery fixtures to the current publication catalog, remote capture budgets and canonical storage owners. Retain immutable test objects across Spring restart and verify physical collection after all four recovery cuts. Reserve fixture ports together and allow the existing ledger and capture retry cadence without changing production scheduling or identity checks. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
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) |
||
|
|
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 <qwen-coder@alibabacloud.com>
* 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 <qwen-coder@alibabacloud.com>
* 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 <qwen-coder@alibabacloud.com>
* 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 <qwen-coder@alibabacloud.com>
* fix(sdk-java): satisfy Checkstyle NeedBraces on the surrogate-normalization helpers (#13803)
The helpers added in
|
||
|
|
21b78c55d5
|
feat(managed-agent): member board tools ride the session_command channel end to end (H4e-b3b) (#13928)
Some checks are pending
SDK Java / Runtime Broker and Managed Agent MariaDB / Java 21 (push) Waiting to run
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Lint & Static (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / web-shell E2E Smoke (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (no-AK, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (ubuntu-22.04) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (windows-2022) (push) Blocked by required conditions
E2E Tests / Build for E2E (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 1/2 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 2/2 (push) Blocked by required conditions
E2E Tests / E2E Interactive - OpenTUI renderer (bun) (push) Blocked by required conditions
E2E Tests / channel-plugin E2E (nightly) (push) Blocked by required conditions
E2E Tests / cron-interactive E2E (nightly) (push) Blocked by required conditions
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Flyway migration version uniqueness (push) Waiting to run
SDK Java / Hosted process fault gates / MySQL 8.4 / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* feat(managed-agent): member board tools ride the session_command channel end to end (H4e-b3b)
Carry decision 13's second wave from the team-member-commands design: members of an open team write and read their leader's task board through the session_command channel. A member's board call mints an outbound entry on her own journal before her Turn parks on an await_command checkpoint (never carrying an input, digest-bound args/reply pairs, per-run budget, batch-locked against Workspace or Runtime siblings); the message relay's new command pass drives it in sender order to the leader — planned → handover → execute → answered, never through consume — and the parked call folds the leader's reply back and marks the entry consumed. The leader executes off the model Turn path: replay-stable receipts for board_writes, an open-team member on its running chain head for fresh commands, the member-facing refusal texts, and a board_read that changes nothing and never receipts.
Both languages carry the contract, the lineage and resource rules, and the store side of the line. The lead's execute answers a not-yet-attached sender's run with a 409 session_command_blocked the relay retries without spending attempts (it can outrun the control plane's attach commit), while a genuine lineage or evidence mismatch stays a terminal session_command_refused. The wait holds a 2-minute bound from the outbox's commit; its timeout tells planned asks 'not sent', handed asks 'reached the leader, reply missing, write possibly landed', and locks the rest of the batch under one text, and the harness descent into await_command now resumes or settles every named wait from the journal alone (decision 14). A member Session declares the three tools exactly behind her lineage, the enabled domain, and her member preamble — never before all three are true — and join keeps refusing 'parent' as a name.
On the aisle where recovery and workspace restore walk the journal, session_command joins the recoverable-domain whitelist so a member holding a commanded write never wedges her later loads, and qwencode classifies await_command like await_agent so a Turn parked on it takes over cleanly. G3 physical: two members write and read back the board, their leader reads it, a member's write races a leader's write without a phantom create, and a member Harness restart mid-wait lands her report anyway — each over a real MariaDB stack with scripted models (HostedTeamMemberCommandMySqlIT, 2/2).
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(managed-agent): admit the scenario lead over the internal TeamLead lane (#13674)
The public Shell admission
|
||
|
|
008c0de746
|
docs(sdk): clarify empty core-tool allowlists across SDKs (#13949)
Co-authored-by: Zhangchenhao-111 <285911435+Zhangchenhao-111@users.noreply.github.com> |
||
|
|
f70344345d
|
feat(managed-agent): recover Hosted G3 parked turns (#13777)
* feat(managed-agent): recover Hosted G3 parked turns Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): preserve G3 recovery across channel integration Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(managed-agent): align recovery fixtures and acceptance evidence Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * 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 <qwen-coder@alibabacloud.com> * 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 <qwen-coder@alibabacloud.com> * fix(serve): keep approval fallback consistent across recovery Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(managed-agent): preserve G3 recovery intent and cleanup liveness Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * 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 <qwen-coder@alibabacloud.com> * test(managed-agent): align G3 cleanup gate fixtures Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(managed-agent): align Java envelope kinds with G3 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
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 <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
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 |
||
|
|
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 <qwen-coder@alibabacloud.com> * 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 <qwen-coder@alibabacloud.com> * 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 <qwen-coder@alibabacloud.com> 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 <qwen-coder@alibabacloud.com> 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 <qwen-coder@alibabacloud.com> * 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 <qwen-coder@alibabacloud.com> 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 <qwen-coder@alibabacloud.com> * 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 <qwen-coder@alibabacloud.com> * 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 <qwen-coder@alibabacloud.com> 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 <qwen-coder@alibabacloud.com> 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 <qwen-coder@alibabacloud.com> * revert: preserve confirmed managed rename recovery contract Revert commit |
||
|
|
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 <qwen-coder@alibabacloud.com>
* 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 <qwen-coder@alibabacloud.com>
* 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
|
||
|
|
a7e26adde7
|
feat(managed-agent): H4d-b session message runtime (#13822)
* feat(managed-agent): H4d-b session message runtime Produce the records the H4d-a contract pinned. The Hosted Shell lanes declare a managed send_message: a parent messages the child tasks it launched by task id, a child messages its parent. A running child gets a message, a completed child gets a continuation that carries its chain's history, and any other ending gets a named refusal. The routing decision shares the child funnel's writes chain with settlement, so a message either opens before the run settles and holds that settlement, or finds the run ended. A new message relay in managed-agent-server fixes each message's target at the handover, commits the receipt into the target together with the input and wake that carry it, and advances the sender as the target accepts and consumes it, with its own V61 ledger for leases, backoff and the orphaned/unknown classifications. The target reads a message as a wake turn and consumes the receipt when that turn completes. The child result relay now settles a child only once no message on its edge owes a handover and its journal holds no pending input, and takes the result from the child's newest settled turn, read from the child's own journal when a message's wake turn ran last. The parent refuses a result commit with child_messages_pending while a message to the run is undelivered. A continuation's first input is composed from the parent's committed instructions and results, bounded below the Hosted prompt limit. session_message joins the enabled domains, the continuation gate opens, the Hosted recovery allowlist admits the domain, and send_message joins the input-preview set in both languages. No public contract change. Refs #12380, #12827, #13744 (slice H4d-b) * fix(managed-agent): close the H4d-b round-1 audit findings Five independent audits of the session message runtime found these. Each one is closed here. A give-up now ends the entry on its sender before it classifies the ledger row unknown: cancelled if the entry was never handed over, accepted if its receipt exists, unknown otherwise. Undelivered now means planned or accepting in both languages, so a message the relay gave up on no longer holds the child's settlement forever. The child result relay reads the child's journal first and its edge after. Only delivered messages waiting for their turn hold the settlement, and only for 30 minutes without activity. The result is the newest settled API or message turn. The settlement names the messageCount the relay read, and the parent refuses it when a newer message exists, which closes the window between the reads and the commit. A committed result is accepted without being recomputed. The message relay decides from the entry as committed now. It pages waiting entries from its ledger index, so an accepted entry whose advance was lost is not stranded. It cancels messages to a child being stopped. A target store fault is a 503 retry, never a rejection. A closed sender's entry is classified delivered when its receipt exists. Waiting entries are reconciled through a new consume operation, which also reloads a target that a replaced Harness dropped. Senders are bounded to 8 messages in flight and 64 in all per child run and direction. A closing parent sends no messages. Refusals from the store become tool errors. A receipt for an unknown run is refused before anything publishes, and the route bounds the content it accepts. Continuation history is now XML-escaped. V61 gains a per-Session index. The migration IT lists V61. The foreground-agent sibling check no longer exempts send_message, to match #13769's recovery. Refs #12380, #12827, #13744 (slice H4d-b) * fix(managed-agent): gate message receipts like other new-work relays - Route receive and consume through the Workspace admission the automation relay runs, and re-attach a Session a prior control-plane process attached through the takeover load for every message verb. - Refuse an accepted step whose input id is not the one the message id derives, pinning the Java relay's derivation to the Hosted one. - Keep the attempt count across a forward ledger step, matching the child result relay. - Record that a failed message wake turn fails the child on purpose. * fix(managed-agent): leave the message ledger to instances with a Harness A Session Store replica runs without a Harness, and its message relay claimed entries it could only fail, counting attempts against them and pushing their real worker behind the backoff. Skip the scan there, as the turn coordinator does. * fix(managed-agent): close the H4d-b round-2 audit findings - Cross-check the edge against the journal read: a parent message the child received between the two reads holds the settlement instead of being closed away unread. - Count any journal event as activity for the message-turn wait, so a long running message turn is never cut off at 30 minutes. - Stop counting a closed child's orphaned outbox as owed, and hold the close of an active child until its own messages left. - Let a committed result or failure stand when a newer turn would re-settle the run the other way. - Read the child journal only when the child holds session messages. - Decide a give-up from the entry as committed after the failed step. - Count only messages not yet handed over against the in-flight cap. - Fail a send_message call on the store's own faults instead of answering a refusal for a message that may still commit. - Join journal text runs a thought separates with a newline, as the API turn result does. * fix(managed-agent): bound the hold on a child's own messages by the give-up A child that can take no further revision (a declined recovery) never lets the message relay's give-up land its sender step, so its outbox entries stayed planned and held the child's settlement and close forever. An entry whose ledger row is classified or past the attempt bound no longer holds either. * fix(managed-agent): fail a child whose message turn never comes Past the 30-minute wait without a journal event, a child still holding an unread message settled from its earlier turn's result, reporting the message as acted on; a turn queued behind a busy shared mount reaches that bound without any fault. Settle it as child_failed instead. * fix(managed-agent): hold a child's message for its task and refuse it once a close began A run attaches as soon as its Runtime binding exists, before the coordinator submits the child's task, so a parent's message could be received first: its wake turn ran without the task and the task then met the busy Session. The relay now hands a message to a child only once the Harness admitted the child's task (its first Turn carries the admission epoch), and cancels it when that task ended without an admission, so the child's settlement is never held by it. The message route now refuses every verb with hosted_session_closing once the Session's close began, like the channel and automation routes, and the close waits for an operation already past that check before it cancels the pending inputs, so no receipt lands after that pass. * fix(managed-agent): stop a stopped run's message turns and answer interrupted send_message calls Main's H4f stop arm decides from the child's API Turn, but a message's wake turn never becomes one, so a stopped run could settle cancelled while a message turn kept running in its child, and a stop met a child whose task had completed but whose messages were still owed as a natural completion. For a child holding session messages the stop arm now reads the child's journal: while a message input waits or runs it sends the child's message route a new `stop`, which keeps the wake pump from starting any message input, aborts a message turn in flight and, once no turn runs, settles the waiting ones cancelled with stop_requested. The natural outcome is the newest settled turn, API or message; a stop the child cannot take yet spends no attempt, and a message input no turn took within the 30-minute bound stops holding the stop. A message handed over before the stop but not yet received is rejected instead of waking the child. A send_message writes only to the journal before its answer, like a team call. Every interrupted-turn route from main's #13829, and the parked round's gap fill that runs before them, now answers it by what it committed: an outbox entry as queued, a continuation as continued, and one that committed nothing as never run where the turn resumes, so the model never sends a committed message twice. The relay's migration moves from V61 to V64: main has V62, and other open pull requests claim V61 and V63. * fix(managed-agent): carry a message stop into the load that attaches its child A stopped run's message stop went through the takeover load every message verb uses when the control plane holds no attachment for the child. That load drives recovery and kicks the child's wake pump before the stop itself arrives, so a queued message turn could start in that window, and the later stop could only abort work already begun. The stop now has its own attachment path: a child this process does not hold is loaded passively with `stopMessages`, which marks its message inputs stopped before the load kicks the pump, under the cancellation authorization a stop needs rather than the grant new work needs. A held child takes the stop directly. * fix(managed-agent): authorize a message stop by its committed request and keep stopped children unloaded The message stop's own load took the cancellation authorization, whose query needs a CANCELLING Turn. Message work never leaves one, and the stop arm skips the Turn cancel once the task ended, so a child the control plane no longer held could not be stopped at all. The load is now authorized like a persisted cancellation, by the committed stop the child result relay read: only the Session's own binding is checked, so neither the missing Turn nor a grant changed since refuses it. Other loads could still attach a stopped run's child without the stop. The stop now goes out before the arm's Turn cancel, whose own load carries none, and the message relay no longer attaches such a child for a consume reconciliation or for the child's own outbox steps; those wait for its close. A message turn that crashed in an earlier process is no longer left owed: the pump skips a stopped Session's message inputs, so the stop runs its aftermath. The Hosted load now marks a resident Session stopped only after the load passes its identity and admission checks, so a refused load changes nothing. The 30-minute message-turn bound counted a resident Session's lease renewals as activity, so it never ended a blocked child's wait; renewals no longer count. * fix(managed-agent): hold a stopped child's message steps only while its stop still settles message work The message relay held every outbox step of a stopped run's child until the child closed. When the stop met a natural end (the task completed or failed on its own, no message work owed), the child result relay settles through its ordinary walk, which waits for the child's own outbox to be handed over before it settles or closes, so the two relays waited for each other with no attempt spent and no bound to break it. The hold now lasts only while the child still owes message work the stop is settling; after that a load starts nothing, and the steps go on. A resident Session's stop flag was set before the passive load checked its tool profile, so a load refused for a profile conflict still stopped the Session's queued messages. Each load branch now sets it only after its own refusals. * fix(managed-agent): bind the claimant in the relay lease CAS and refuse a closing child's message to its parent Both relay ledgers guarded the lease takeover with the owner their read saw rather than the claimant. Two workers that both read an expired lease could therefore both win it: the first renewed it as its own, and the second still matched the stale owner. The guard now binds the claimant, so the second meets the first one's fresh lease. A child's send_message to its parent took no closing flag, so a call in flight when the Session's close began still opened an outbox entry the closing sender could only orphan, while the model was told it was queued. It now refuses like the parent-to-child arm, after a committed message's replay. * test(managed-agent): expect send_message among a Shell-lane Session's tools in the Hosted IT driver The Hosted workspace tool-turn integration driver pins the exact tool set a Shell-lane root Session advertises. Since this change that set carries the parent form of send_message beside the Agent tool, so the MySQL process gate failed on its first model call. |
||
|
|
1e1c4a4aee
|
feat(managed-agent): worktree admission for managed child agents (#13753 I2) (#13841)
Some checks are pending
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (no-AK, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (windows-2022) (push) Blocked by required conditions
E2E Tests / E2E Interactive - OpenTUI renderer (bun) (push) Blocked by required conditions
E2E Tests / channel-plugin E2E (nightly) (push) Blocked by required conditions
E2E Tests / cron-interactive E2E (nightly) (push) Blocked by required conditions
E2E Tests / web-shell Browser Regression (push) Waiting to run
Qwen Code CI / Desktop Shell (ubuntu-22.04) (push) Blocked by required conditions
E2E Tests / Build for E2E (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 1/2 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 2/2 (push) Blocked by required conditions
SDK Java Flyway Open PRs / Re-check open PRs against main (push) Waiting to run
SDK Java / Flyway migration version uniqueness (push) Waiting to run
SDK Java / Runtime Broker and Managed Agent MariaDB / Java 21 (push) Waiting to run
Verify Landlock helper / Native Linux CLI boundary (push) Waiting to run
Verify Landlock helper / Native Windows settings and streams (push) Waiting to run
Verify Landlock helper / arm64 (push) Waiting to run
Verify Landlock helper / x64 (push) Waiting to run
SDK Java Contract Open PRs / Re-check open PRs against main (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Hosted process fault gates / MySQL 8.4 / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* feat(managed-agent): worktree admission for managed child agents (#13753 I2) I2 of the isolation slice (#13753): the hosted Agent tool can run a child agent in its own child Workspace, the Git worktree I1 (#13781) built, and the child's work merges back before the parent sees its result. The model opts in per launch with `isolation: "worktree"`, the legacy Agent tool's name and value; without it the child is `shared` as before. The launch records `workspaceMode: "worktree"`, and admission accepts it only on a host that serves child Workspaces: the control plane sends `childWorkspaces: true` on every hosted create and load, `qwen serve` keeps it on the resident Session without persisting it, and only then does the Agent declaration show `isolation`. Elsewhere a worktree launch is refused as `workspace_mode` before anything commits. The team variant of the Agent tool (H4e-b1) gains `isolation` the same way, so a member may run isolated. Startup now requires the durable local-process Broker for child Workspaces, the only one that closes Workspace Sessions. The relay requests the child Workspace before it creates the child and waits on its heartbeat, at no attempt cost, while the scan prepares it; a `ready` row creates the child bound to it. When the child's Turn completes, the relay asks for the merge, admits the child's close, and commits the result only once the row records an outcome. The terminal receipt then carries `workspace` (outcome, code, conflict paths bounded to 16 KiB, the result pin when the work did not land), built from row fields a later discard leaves alone so a replayed commit is byte-equal. The foreground answer, the recovered wait, the recovery gap fill and the background notification all show that outcome to the parent's model, with child-chosen paths quoted and stripped. A failed or cancelled child's Workspace is discarded; a completed child's work is never discarded unpinned (an over-bound answer or a give-up after a completed Turn asks for the merge, and a merge already asked survives later failures). The close cascade asks for the discard and never waits for it, keeping the run's scope open while that request is owed. I1's finish rules change accordingly: a finish request is recorded while the bound child Session runs and runs once it is closed; a discard may replace a merge that has not started, and the step leaving `ready` compares the request it read; an ended merge keeps its outcome through a discard that cannot finish. Design: docs/design/2026-10-10-managed-child-worktree-admission.md (+ zh-CN); the I1 design notes the amended decision 8. * docs(managed-agent): qualify the I2 completed-work guarantee by the parent's close The relay never discards a completed child's work unpinned, but a parent that closes before the asked merge starts still discards it (decision 9). Say so in decision 8, in both languages, and mend the sentence the earlier edit left broken. |
||
|
|
6386bb24d0
|
feat(managed-agent): restart-recoverable foreground child wait (fixes #13708) (#13769)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Lint & Static (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / web-shell E2E Smoke (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
E2E Tests / Build for E2E (push) Waiting to run
SDK Java Contract Open PRs / Re-check open PRs against main (push) Waiting to run
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (no-AK, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (ubuntu-22.04) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (windows-2022) (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 1/2 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 2/2 (push) Blocked by required conditions
E2E Tests / E2E Interactive - OpenTUI renderer (bun) (push) Blocked by required conditions
E2E Tests / channel-plugin E2E (nightly) (push) Blocked by required conditions
E2E Tests / cron-interactive E2E (nightly) (push) Blocked by required conditions
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java Flyway Open PRs / Re-check open PRs against main (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / Runtime Broker and Managed Agent MariaDB / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Flyway migration version uniqueness (push) Waiting to run
SDK Java / Hosted process fault gates / MySQL 8.4 / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* feat(managed-agent): restart-recoverable foreground child wait (fixes #13708)
A foreground child-agent call skipped the Runtime reservation and, with
it, the only durable checkpoint of its wait — agent calls write no
tool.intent. A Hosted process dying into the wait left the checkpoint at
the pre-agent model phase, so recovery could only decline the parked
Turn (model_start), every later Turn wedged at
hosted_turn_recovery_required, and close/delete could never pay the
cascade debt. Introduce the durable agent wait as a first-class
continuation: an await_agent checkpoint phase with an agentWait group
committed by the foreground admission under the same transaction
discipline as the Runtime batch, replay-safe boundaries, and a
resolve that marks each fold consumed and advances to
model_output_committed carrying the all-consumed group so a second
crash classifies the wait instead of reading settled work as a false
terminal.
Recovery classifies the wait (active and passive identically) and never
commits; the wire protocol gains the phase with every outcome always
known, so the coordinator's existing continue/cancel admissions drive
it. The continue and cancel routes re-enter the wait through their own
resume arms: the admitted runs fold through the journaled exactly-once
dedupe, and the parked round's never-reached calls are answered —
ledger-honest, so an admitted orphan (admitted in the gap between an
earlier fold and its own wait commit) drives to its own terminal and
folds truthfully instead of minting a fabricated never-admitted answer.
The cancelled family's discipline (settleParkedTurnCancelled) is
mirrored for the agent wait, and the journaled predicate and both
abandoned texts are shared by every arm.
Verification: core checkpoint 28/28 and harness factory 41/41; CLI
recovery 47/47, child-agent turn 25/25, tool turn 206/206, harness
session 305/305, dedicated wedge suite 8/8 (points 1/2 re-entry and
exactly-once, cancellation takeover, second-death orphan fill); Java
HostedHarnessClientTest 57/57; managed-agent-server verify green;
whole-repo typecheck, ESLint and Prettier clean. Mutation witnesses W1
(admission commit), W2 (journaled dedupe) and W3 (all-consumed replace
guard) each proven RED then restored byte-identical. Design:
docs/design/2026-10-09-foreground-child-wait-continuation.md (EN + zh).
* fix(managed-agent): pin admitted-orphan fold discipline and witnesses
Adversarial-audit round on the #13708 continuation: the cancel route's
gap fill ran without the inline-fit predicate, and an oversized admitted
orphan answer would have errored the recovered Turn instead of folding.
The fill now folds such answers with the same fit+marker discipline the
live arm uses, both route call sites pass their own fit lambdas, and the
wedge suite gains witnesses for the three previously unwitnessed shapes:
mid-flight admitted orphan folding its late settlement truthfully, an
aborted signal abandoning the orphan honestly while its ledger stands,
and an oversized admitted-orphan answer folding with the truncation
marker instead of erroring.
* fix(managed-agent): exact inline-fit predicate for the gap-fill fold
The gap fill's fit test drifted from the record it actually commits: the
fold wrote a model-less record while a per-caller lambda measured other
shapes, so a child answer near the 64 KiB inline bound could pass the
predicate and land oversized — or miss and fold a boundary answer into an
avoidable error. The fill now records with the resumed model string and
measures one exact template whose field widths match the committed
record byte for byte (uuid, assistant parent, ISO timestamp, model); the
per-callsite lambdas are gone and the truncation witness still lands the
marker. Both routes consolidate on the same predicate.
* fix(managed-agent): child-wait recovery — takeover adoption, honest fills, funnel arms
Review round-1 Criticals on the #13708 continuation, all adjudicated
valid and fixed with discriminating pins:
- core (R1-21): resolveAwaitAgent adopts the takeover activation when
the wait advances past the last fold, so a replacement owner's next
tool batch is not refused as work of a prior activation; partial
folds keep the parking activation.
- serve (R1-1): the gap fill keys the parked round on the wait's own
modelMessageId set, so a later ordinary-tool round can never be
minted a fabricated agent answer.
- serve (R1-23): each fold is gated on the journaled set while the
replay-safe marks still run, and the filled count reports only real
writes, so a replayed fill is silent.
- serve (R1-24): a background-completion orphan folds the live arm's
started receipt instead of being polled forever.
- serve (R1-3): the cancellation separation refuses to short-circuit
an agent wait, and the interrupted-turn funnel settles it exactly
like the takeover cancel; settleCancelledAgentWaitRuns and
fillParkedRoundAgentGaps relocate into hosted-runtime-recovery for
the funnel caller, with parkNeedsNoRuntimeSettlement exported for
the wedge suite.
Witnesses: W1 park gate flipped RED, W2 funnel arm cut RED, W3
background arm unreachable RED (poll-timeout), each restored
byte-identical. Wedge suite 14/14, recovery 64/64, child-agent 26/26,
route suite 338/338, monitor-wake 33/33, factory 43/43; tsc and
scoped eslint/prettier clean.
* fix(managed-agent): child-wait hardening — boundary validation, compat encode, recovery wording
Review round-1 Suggestions (code half), all adjudicated valid:
- core (R1-6): both write-side constructors refuse the shapes their own
assertPhaseShape rejects — an empty agent-wait group and a
model_output_committed forwarding an unconsumed carried group.
- core (R1-29): the encode omits agentWait whenever no group rides the
checkpoint — an absent key parses null on both reader generations, so
a pre-agentWait daemon can still open every non-agent checkpoint.
- core (R1-30): commitAwaitAgent refuses duplicate childRunId /
functionCallId and already-consumed runs at the boundary instead of
bricking the session at the next checkpoint read.
- core (R1-53): commitAwaitAgent's turn parameter is required — the
absent branch silently skipped both the binding guard and the
activation adoption.
- core (R1-14): the consumed-run comment now names the group's real
lifetime (replaced by a later wait or the Turn boundary, never the
Turn boundary alone).
- cli (R1-25/R1-52): one authority for the exactly-once predicate —
journaledToolResultIds takes an optional pre-projected journal, the
gap fill passes its own projection instead of walking the journal
twice, and answerAbandonedTurnCalls drops its hand-rolled copy.
- cli (R1-49): the never-admitted answer is per-family wording — the
continue route and the interrupted-turn funnel name the interruption
(nothing was cancelled); the cancel route keeps its own text. fill
now takes gapText explicitly.
- cli (R1-51): the cancel settlement decides its two shapes off the
phase-shape invariants outright (agentWait null or not) instead of
dead ternaries and implied conjuncts.
- cli (R1-7): the gap fill's fit predicate is the exact template only —
the messageFitsInline dead switch is gone, and the oversized case now
drives the production 64 KiB bound itself (65513-byte child result,
under the 65536 child limit but past the record bound).
Pins: checkpoint suite 32/32 (R1-6 guards, R1-29 key, R1-13
phase-shape + double-consume), factory 46/46 (R1-13 empty batch,
R1-30 duplicates/consumed, restated-swap keeps the set-conflict arm),
wedge 14/14 (fill callers re-keyed to the recovery wording), recovery
64/64 (the undefined-agentWait mock shape stays judged by the carried
null). tsc clean on both packages.
* fix(managed-agent): child-wait test-depth — route witnesses, orphan arms, poll evidence
Review round-1 Suggestions (test-half), all adjudicated valid and pinned:
- routes (R1-8): parkAgentWaitTurn mints the wedge through the
production admission itself (execute → acceptChildAgent →
commitAwaitAgent), with the abandoned fold's write sabotaged so the
owner dies at the wait; the continue case proves the gate admits the
parked wait (witness W5: reverting it answers 409), the cancel case
proves the cancelled takeover folds the abandoned answer, advances
the checkpoint past the wait, and closes the Turn exactly once.
- fill (W4, found while keying the fixture to the production child-run
id): the fill must never poll the wait's own calls — an admitted but
unsettled wait run hangs the fill forever; waitedCalls skips them.
W4 deletes the skip and the interrupted-turn pin times out RED.
- wedge (R1-10): a failed admitted orphan folds its terminal cause
('Child agent run failed'), never the never-admitted answer.
- wedge (R1-11): consumption is caller-owned and asserted — the resume
arm's queue collects the settled run, the abandoned arm leaves it
untouched, the fill's consume callback and the relay's delivery
state ('accepted') are pinned; the dead void-collection is gone.
- wedge (R1-36): the mid-flight poll's wait is asserted as a ledger
read count greater than one, never a wall-clock coincidence.
- wedge (R1-8 folded gaps): the charter names what the suite actually
covers, CHILD_RUN_ID derives from PROMPT_ID by hostedChildRunIdFor
instead of the mock broker's runtimeSessionId, the fill callers pass
the production gapText + children, and the last case is renamed to
what it asserts.
- recovery tests (R1-32): the await_agent report's runtimeSessionId is
asserted as hostedRuntimeSessionId(promptId) on both load shapes.
Wedge suite 15/15, route suite 340/340, recovery suite 64/64, tsc
clean; merge-conflict resolution verified marker-by-marker per the
house rule after the H4d-a baseline merge.
* fix(managed-agent): child-wait wire + contract — Java predicate pin, design-doc truth pass
Review round-1 Suggestions (Java/docs close-out):
- java (R1-15): agentWaitWithUnknownOutcomeIsNotReady — an await_agent
recovery carrying any unknown outcome is neither continuation- nor
cancellation-ready; witness W6 drops the all-known conjunct and the
pin REDs (restored byte-identical, suite 63/63).
- docs (R1-16): the broken nested list item in the EN design doc's
rationale bullet re-joined into one paragraph.
- docs (R1-31 ×5, both languages, synchronized): §1 names the one
spreading constructor and why it is safe plus the absent-when-null
encode; §2 records turn-required and the boundary validation; §4
records runtimeSessionId=hostedRuntimeSessionId(promptId) and its
inertness; §5 defines the gap fill (inputs, three answer shapes,
exactly-once discipline, per-family wording); risks now point the
wire-drift mitigation at the predicate-level tests; the validation
plan attributes coverage to the layers that actually hold it
(classification-only recovery, turn arm, routes, dedicated suite)
and marks close/delete rig-facing with acceptance criterion 3 to
match.
- tests (R1-58): the carried-group case documents it pins the
constructor's contract rather than a production transition.
* fix(managed-agent): child-wait round-2 — carried adoption, cancel-sweep abort, lazy mount
Real-stack re-review on
|
||
|
|
c3968ace2f
|
feat(managed-agent): H6b/H6c automation runtime for persistent definitions (#13598)
* feat(managed-agent): H6b/H6c automation runtime for persistent definitions The runtime part of slice H6 of #12827 (stage H of #12380), after the H6a record contract (#13536): definitions, the single-claim scanner, the occurrence ledger, overlap and catch-up, and persistent execution. - Definitions are created, revised, read and retired under the Workspace-bound Session their creator owns. The control plane relays each mutation to the Session's Hosted Harness, whose authority publishes the prompt as a Session resource and commits the definition revision; the control plane replays by Idempotency-Key and answers 202 with the committed resource. The two public automation routes move from planned to partial and gain the mutation routes and the detail read. - The scanner derives each armed definition's due slots on the host tz database (a shared slots fixture pins the cron semantics, DST gap and fold rules in TypeScript and Java), claims the definition under a lease with a fencing token, records every occurrence decision (fired, skipped, missed, or unknown after bounded retries) in the V53 ledger, and fires one idempotent Harness operation whose claim row was durable first; a manual run is the same operation keyed by its Idempotency-Key. A scanner that lost its lease can move neither the watermark nor the ledger; a manual run holds the same lease. - Firing commits the run's claim revision and then, in one journal transaction, its dispatch revision with the run's input and wake; the embedded wake scheduler runs the input as a text turn; the journal's turn settle settles the run on the settle path and again on open, and the close path ends a never-run input as cancelled with a proven execution. - Run identity is derived from the definition and the occurrence in both languages; the authority and the Java store refuse an underived id or a run that does not bind to its live definition at the current revision, so two claims of one occurrence meet one committed run. - Both domains are enabled for submission for the persistent target mode only (MANAGED_SESSION_ENABLED_SCHEDULE_SESSION_MODES); per_run and the delivery policy stay refused until H4b and H5b/H5c land, and the scanner and mutation routes stay behind qwen.managed-agent.automation.enabled. - Closes the H6a verification follow-ups: promptRef is Session-scoped and closed by both stores, the shared fixture gaps are pinned, and the Java text validator checks its clauses in the shared order. Design: docs/design/2026-10-07-managed-automation-runtime.md (bilingual); the H6 design's status and slice table are updated in both languages. * fix(managed-agent): close the three runtime findings on H6b/H6c R2-1 (P1): a mutation relay carried a fresh operationId per attempt and the funnel deduped on content alone, so a request whose answer was lost between the Harness commit and the control-plane command row could be retried into committing its content over a newer revision (create rev 1 → update rev 2 → retry commits rev 3, replayed=false, in both languages). The relay now derives the operationId from (tenant, Idempotency-Key), the funnel defines/retires answer the operation's originally committed revision from the journal (rebuilt across reopens) before touching the chain, and the control-plane mirror never regresses past the revision it tracks while the remembered result is the operation's own. R2-2 (P1): runAutomationOperation skipped the Workspace admission every other new-work verb runs, so a cached attachment kept firing and mutating after the creator's grant was revoked. It now runs requireReadyForNewWork before every relay. R2-3 (P2): a prompt within the raw 64 KiB bound fails its fire when the wrapped, JSON-escaped input envelope exceeds the 65,536-byte inline resource bound, stranding an admitted/intent run. Admission now computes the exact worst-case wrapped input of the merged definition (largest route-admitted identities, real escaping) and refuses the definition. One residual is deliberately deferred: a mutation relay that matched the committed content appends no revision, so it leaves no operation fact; its lost-answer race with a later commit needs a journal-level no-event operation record and is out of this fix's scope. Verified: packages/core managed-runtime 2748/2748; cli funnel 13/13, hosted-harness-session 238/238, hosted-monitor-wake 18/18; managed-agent-server 1304 tests, 0 failures (109 classes); npm run typecheck/lint, Prettier, checkstyle and spotbugs green. * fix(managed-agent): persist no-op mutation honors and bind replay to target and content R3-1 (P1): a mutation honored without a revision (content identical to the committed definition) left no operation fact, so its lost-answer retry against a moved chain re-applied the old content as a new revision. The honor is now committed as an `operation.replayed` journal event — the 18th member of the closed kind union, synced across TypeScript, Java and the store-v1 and event-envelope-v1 contracts — naming the honored revision's recordRef under the same derived operation identity, so the retry answers the honored result across restarts. R3-2 (P2): the operation replay answered by operationId alone, so a retire re-driven under one id against another target returned the first definition's cancel (and the control plane even retired the second one's mirror), and a define under one id with another body got the original result where a 409 was owed. Every replay now verifies the record's target, and for defines the merged requested content digest; mismatches throw AutomationOperationConflictError -> 409 automation_operation_conflict. Verified: cli funnel 15/15 (two new regressions incl. reopen survival and both conflict classes), full hosted-harness-session 238/238, core managed-runtime + contract replays green (three timing flakes re-passed cold), Java managed-agent-server 1306 tests 0 failures (AutomationScannerTest 23/23; both new service scenarios pinned), typecheck/eslint/checkstyle/ spotbugs green. * fix(managed-agent): close the review wave, the real-stack findings, and their regression gaps The second-stage review (33 threads) and the real-stack verification (F1–F5) land in one round, with regression tests in both languages for every behavioral change: Real-stack findings (candidate patch adopted from the real-stack rig): - F1: the next fire stays suppressed as skipped(overlap) while the target Session holds an open public Turn — the wake pump would otherwise start a queued automation input ahead of the dispatcher's retried Turn and a recurring definition could starve it (128 s / 190 s measured). - F2: a Session attached before (`harnessBootId` set) and absent from the connector cache is re-attached through the same takeover load a Turn takes, so a control-plane redeploy never strands automated Sessions on hosted_session_already_attached. - F3: a run whose turn the Harness stopped inside settles once as failed with execution outcome_unknown — the H0b pairing rule now admits exactly that pair (TS, Java, schema, and the flipped failed-with-unknown-outcome fixture) — instead of running forever and starving its definition. - F4: the automation scanner runs on a dedicated managedAutomationScheduler, never the one-thread default pool that also carries the materializer, retries and recovery ticks (5 ms / 5 ms measured). - F5 was already closed by the N4 round (operation replay markers + target/content binding); the client's three wire guards (202-only, settled-only, operationId echo) are pinned at the qwencode SDK level now. Review findings: - Behavior: definitive 4xx route refusals settle the occurrence as skipped with the refusal as reason instead of 64 lost-answer retries into `unknown` (manual runs answer 409 with the reason first, 202 replay after); the update's idempotency digest binds session_id so a same-key different-session request conflicts instead of replaying; CronSlots.compile accepts ASCII digits only (ARABIC-INDIC no longer parses to minute 3); the close path skips its whole-transcript projection entirely when no notification of the named sources awaits; the /automations/operations error classification is typed (AutomationOperationConflict, a new ManagedSessionWritesStoppedError -> 503, a new ManagedSessionModeGateError -> 409 mode_disabled, ManagedSessionConflictError -> 409, plain record errors -> 400), deleting five hand-listed message substrings. - Contracts and specs: store and event-envelope fixtures gain the 18th event kind `operation.replayed`; the slots fixture gains Pacific/Apia with whole-day date-line jump witnesses; OpenAPI gains the create request's required fields, 503 declarations on all automation mutations, automation_busy and invalid_automation code mentions, UTF-8-byte units for goal/prompt, the TaskState $ref on run.state, occurrence_key cursor wording, and bounded catch_up invariants. - Docs: the H6 slice table no longer claims the domains stay disabled; the zh-CN H6 doc recovers its later-slices paragraph; Files-affected lists every touched file; the mode-gate comment and PR deferral stop pretending the gate checks only the first revision; the backoff cap reads 256 s (not 5 min); the slot-CPU wording is result-bounded rather than enumeration-bounded; README says the same. - Test scaffolding: the H2-backed scanner suite deletes rows between methods (tick iterates all schedule tables), the Harness fake answers an already-committed run before its refusals (production order), fixtures type their ChatRecord payloads, the committed input decodes through its own decoder, a multi-definition session distinguishes the record revision from the domain counter, a workspace-profile load now exercises the restore verifier's automation domains, and the route settle wiring gains open-path recovery, close-path model-free settle and runner-catch coverage. Verification: packages/core managed-runtime full suite green (hook-scale timeouts recur only under parallel load and pass cold); cli hosted-automation 17/17, hosted-harness-session 242/242, hosted-monitor-wake 19/19; managed-agent-server module full suite green (AutomationScannerTest 31, QwenHostedHarnessConnectorTest 40, Qwencode HostedHarnessClientTest 37); npm run typecheck/lint, Prettier, checkstyle and spotbugs all green. * fix(managed-agent): enforce the advertised definition goal bound maxGoalBytes was exported as one of this module's limits but had no read site: the goal bound arrived only incidentally from the record contract's generic text bound. assertAutomationDefinition now enforces its own constant directly, with a refusal named for it (the parse probe behind it keeps the same invariant if the constants ever diverge). * docs(managed-agent): sync the EN runtime design's backoff clamp with the code and the ZH twin * test(managed-agent): admit V53 in the W1c migration upgrade pin * fix(managed-agent): keep store faults retryable and bind idempotency across operation kinds Two findings from the fourth review wave on PR #13598: A store HTTP or transport fault surfaced to the route through the ManagedSessionRecordError superclass, so a fire_run whose prompt read hit a transient 503 was answered 400 — and the scanner's definitive-4xx branch settled the occurrence skipped forever, while redrive only visits firing rows. assertReplayContent's validation catch likewise wrapped promptOf's read into a conflict, so an honest retry of the identical request became a 409. The route now classifies both store fault classes as a retryable 503 and the content check reads the prompt outside the validation try; only the request's own shape is still a 400/409. The mutation replay looked up history by (operation, operationId), so a cross-kind retry under one Idempotency-Key — a retire(K) after a committed create(K), when the control plane lost its command row before remember() — slipped past and really cancelled the definition. The funnel now consults the journal for any committed operation under the derived identity and refuses another kind with automation_operation_conflict, matching the public one-key-one-operation constraint; the journal rebuild makes the check hold across a Harness restart, and the fake mirrors the rule at control-plane level. Regression tests in both languages: funnel cross-kind refusal (same session and across a reopen), a store fault passing through the replay content check as itself, a route-level fire answered 503 whose claim still dispatches after the store recovers, a scanner fire answered 503 staying firing for the redrive, and a service-level cross-kind retry conflicting with the mirror left live. * fix(managed-agent): refuse a cross-Session idempotency-key reuse before relaying The operation identity derived from (tenant, Idempotency-Key) is tenant-scoped, while the journal proving it is Session-scoped: create(K, S1) committed and mirrored, the command row lost before remember(), a create(K, S2) would relay to S2's Harness and mint a second definition under the same scheduleId, which the mirror then shadows behind the first Session's resource. create() now checks the durable mirror for the derived scheduleId first and, when it landed on another Session, answers automation_operation_conflict instead of relaying; identical retries on the same Session still pass through to the funnel's replay. Regression: the lost-row cross-session create conflicts with nothing relayed and the mirror still naming the first Session. * fix(managed-agent): claim automation operation identities before the side effect The mirror check added for the cross-Session reuse only sees answers that already landed: a create whose Harness committed but whose answer was lost has neither a mirror nor a command row, and a concurrent request can pass the check before the first one mirrors — both windows then committed the same derived scheduleId a second time, shadowing the first definition. create() now claims (tenant, Idempotency-Key) with an atomic pre-relay insert into the command ledger: the primary key arbitrates concurrent racing, a claim held by a different request body conflicts idempotency_conflict before anything relays, and the identical request falling back into an unanswered claim redrives it so the funnel replays what it committed. remember() settles the claim row rather than adding a second, and the same-Session mirror guard stays for rows the control plane lost on its own. Regression: a lost-answer create keeps its claim so the cross-Session retry conflicts while the owner redrives to its committed revision, a foreign mid-flight claim conflicts with zero side effects, and the lost-answer retry path asserts the claim-shaped row. * fix(managed-agent): guard claim settlements by owner, claim every ledger mutation, release on definitive refusals The shared remember() settled by (tenant, key) alone while update and retire did not claim first: an update that had passed its replay check before a concurrent create could overwrite the create's completed result_json — the row kept the create's digest and scheduleId, so the create's honest retry was then answered with another automation's resource with replayed=true. update() and retire() now join the pre-relay claim protocol create() introduced: every mutation using the command ledger binds (tenant, Idempotency-Key) to its request before any side effect. The settlement itself is guarded twice — request_digest and actor must match the row's owner, and only an unanswered claim can be filled — so a late re-driven mutation can neither steal the row nor rewrite a completed result; the guarded settle is pinned red against the pre-fix unguarded form. Since only commit-proven refusal answers exist on the Harness, a definitive 4xx now releases its own unanswered claim instead of burning the key, so a fixed re-send of a refused request succeeds; the retired-definition replay branch settles its own claim the same way. * fix(managed-agent): release claims only for refusals proven pre-commit A blanket 4xx release read too much into the answer: invalid_automation_operation answers both a malformed new request — committed nothing — and an integrity failure while reading a previously committed resource, where the original operation may well have landed. Releasing the claim in the second case orphaned exactly the definition the claim was binding, opening the same-key-on-another-Session window the protocol exists to close. Claims are now released only for 4xx codes that answer exclusively before any commit (not found, mode gate, conflict, stale revision, retired, quota); a proofless answer keeps the claim, so the other request conflicts and the owner still re-drives. The fake gains a pre-commit mutation-refusal seam, and the regression pins the proofless 400 keeping its claim, blocking the second Session and settling on the owner's retry — verified red against the blanket form first. * fix(managed-agent): release claims only on refusals audited pre-commit The 4xx-minus-400 partition still read proof into answers it cannot have: a re-attach that finds the Session running a Turn answers 409 hosted_session_already_attached — an attachment state, not a commit verdict — and a takeover that misses the Session answers hosted_session_not_found, equally mute on whether the mutation landed. Releasing the claim on either orphans a definition the original Session may well hold. The release set is now an audited enumeration of the funnel's own business refusals, each with a single pre-commit answering path — automation_not_found, mode gate, operation conflict, stale revision, retired, count limit — closer to exhaustive proof than to convenience; every other answer keeps the binding. The attach-conflict regression mirrors the proofless-read one: same claim surviving, second Session conflicting, owner re-driving to its own answer; verified red against the loose form first. * fix(managed-agent): release claims only on request-determined verdicts A quota refusal is a verdict about the moment, not about the key: the claim row is shared by every attempt under one Idempotency-Key, so delivering an early attempt's automation_count_limit 409 and releasing the row dissolves the binding its later sibling may already have committed against — after which a third body under the key mints a second definition and the sibling's delayed success settles to the wrong mirror. What may release is now exactly what cannot move between attempts: the mode gate, decided by the definition's sessionMode against the deployment's admitted set, identically every time the same request is sent. Capacity, existence, binding, revision and terminal verdicts all evaluate shared state and keep the claim; the quota regression mirrors the family's other pins — refused attempt, foreign body conflicting, sibling committing and settling — and was verified red with quota back in the set first. * fix(managed-agent): map wake-turn ids to path-safe Broker sessions, and refuse fires a blocked Session can never run The round-2 real-stack pass found two walls. Every automation turn whose model calls a Workspace tool died at the Broker's path-safe Runtime Session id rule (`arun_…:input` rejected at acquire), blocking its Session: wake input ids are committed as the turn id, so every Broker handle built from a prompt id now maps a non-path-safe one to a stable `wake-<sha256>` instead (`hostedRuntimeSessionId`); the mapped form is itself path-safe, so the map stays idempotent wherever lease/record paths already carry it. And a recovery-blocked Session kept answering 202 to fires, committing runs that can never start while its slots kept scheduling: `fire_run` now answers 409 `hosted_session_blocked` unless the occurrence's run is already dispatched (a pure replay answer carrying no new fact), the scanner recording the slot skipped with that reason. The run the F3 path settles `failed`/`outcome_unknown` now consumes its crashed input too — a `turn_result` settles its turnId — so a reload stops classifying the same crash as recovery and re-blocking the Session every time. Regression: the id mapping (stable, idempotent) and a route-level wake turn whose tool call reaches acquire under the mapped id (red on the raw form); a blocked Session refusing a fresh fire while replaying its crashed run, consuming the crash, and firing after reload; the scanner recording `skipped(hosted_session_blocked)` on the new refusal. Real-stack F1/F4/F5 and the claim protocol were re-verified by the report; the dead journal after a missed activation renewal is pre-existing on main and goes to a separate issue. * fix(managed-agent): settle the runtime park and silence of a crashed wake turn The round-3 real-stack pass found two new walls past F6/F7. When the Harness crashes inside an automation turn's tool call, the F3 settlement left the checkpoint parked at await_runtime — every later turn on that Session refused (await_runtime is not a model-start phase) and the wake Broker session held the Workspace execution lease for every Session on it. When the crashed turn's prompt was text, its naked user record merged into the next turn's model history, so the crashed instruction ran a second time inside an unrelated run. In the F3 recovery path: when the crashed turn's checkpoint holds in-progress Runtime items, the existing stop/cancel helpers drive them to terminal, settleParkedTurnCancelled records each cancellation as its cancelled tool_result — so the checkpoint advances out of await_runtime and the history stays well-formed — and the wake session's Broker lease is released. The Session unblocks only once that park is provably settled: a failure to even read the checkpoint, or to stop or release any part of it, keeps the block rather than letting a fire run against an unproven park. The consume write of the crashed input's turn_result gains bounded retries on SessionTranscriptChangedError, so a concurrent route claim cannot drop it and resurrect every reload classifying the same crash as recovery. The fire refusal itself is unchanged: while blocked, anything that would commit a new fact answers 409 hosted_session_blocked. For the history side, runHostedHarnessTextTurn's assembly now excludes every user record whose prompt id ended with an error or cancelled turn_result (excludeTerminallyEndedUserRecords): a turn that ended without an answer is over, so the next turn never meets the crashed instruction as live work — pinned one level up in the runner's own suite (the crashed prompt is absent from the chat's setHistory while an answered older prompt passes through). * fix(managed-agent): one aftermath for a crashed wake turn, reconciled and retried until proven Round 4 of the real-stack pass found the round-3 recovery still wedging in three shapes and the F9 fix dead on the real path. A crash inside a wake turn's Runtime call whose park settlement fails once consumed the input anyway — after any restart nothing classified the crash again, so the Session and its Workspace lease wedged for good (one transient Broker 503 sufficed). With overlap skip, Spring skipped every later slot, so nothing ever loaded the crashed Session at all and its wake Broker session kept the Workspace execution lease while every other Session span on workspace_busy with no upper bound. The first user Turn to load a crashed Session raced the recovery behind the load and failed post-admission. And the round-3 history exclusion read turn_result records from a projection that never carries them, so the crashed prompt still merged into the next turn's model history. The crashed wake turn now has one aftermath, keyed on the journal and the checkpoint rather than on which settle ran before it: the run fails with its execution unknown; the parked Runtime executions stop to terminal and settle cancelled — each cancelled tool_result committed once so the checkpoint leaves await_runtime with the call and its answer paired; the wake session releases its Broker lease, re-attempted on every pass since the journal cannot record whether an earlier pass landed it; and only then does the input's own turn_result consume it, with a transient journal conflict retried. An unproven settle keeps the block and the input pending, so the wake pump's blocked cycle — re-armed across a dead pass — the next load, and the fire that loads the Session all retry the same sequence instead of hinging a permanent wedge on one transient fault. For the case nothing loads the Session — overlap skip darkens the definition — the scanner, before recording a count-based refusal (never the open-Turn one), asks the Harness a fourth verb, reconcile_run, which commits no new fact, classifies and settles the crashed wake turn, and answers its run; the decision prices the count off those answers rather than the lagging mirror. A prompt arriving while the park is unsettled is refused pre-admission with the retriable 409 hosted_turn_recovery_required. And a turn that ended without an answer keeps its prompt out of every later turn's model history, filtered against the journal's turn.settled events at both history builders — the dead record-layer exclusion is gone, replaced by a pin through the real builder path: with the exclusion deleted, the new test fails. Witnesses: six new Harness pins (park settle after a Broker fault with cancelled tool_result / lease release / in-process unblock, the reconcile route both polarities, consume retry, history exclusion, pre-admission prompt refusal) and three Java scanner pins (a wedged run reconciled into the very slot that asked, an unanswered reconcile keeping the refusal, an open Turn never triggering a reconcile); the fake gains the reconcile verb. The AutomationScannerTest shared-row hygiene now sweeps the extension record/resource tables, closing a latent stale-mirror revival the new pins exposed. * fix(managed-agent): relay automation operations through #13163's workspace admission split #13163 split requireReadyForNewWork by intent (action responses probe the mount, new work takes the full admission). The automation relay added by this branch still called the two-arg form, so main's build broke on the merge: fires, defines, retires and the new reconcile_run all drive new work — take the full `workspaceExecution.authorize` admission, the same check a fire's relay took before the split. * fix(managed-agent): re-check the turn slot after the wake-park probe before claiming The prompt route's admission mutex was atomic because no await stood between its `session.active` refusal and its claim. The wake-park probe inserted one: while the route awaited, the pump's wake turn could pass its busy re-check and start, and the route then overwrote the claim unconditionally — two concurrent turns on one journal, the exact wedge the unsettled-inputs guard exists to prevent. Re-read `session.active` synchronously right before the claim, restoring the check-then-claim pair's atomic discipline (the wake pump's own runner follows the same re-read-after-await rule). A deterministic interleave pin is not offered here: the window needs an injection point inside the route's async span, which the route does not have today; the guarantee rides on the re-established discipline and the file's existing concurrency witnesses. * fix(managed-agent): price a reconciled refusal off what the mirror has not caught, never doubly-subtracting Audit round 1 (lane D). Two questions in the rebuilt refusal price after a reconcile: 1. The settlement the reconcile just made the Harness commit IS also the record mirror's correction: the host store's commit path applies the extension record synchronously (ManagedSessionStore. The mirror is not lagging the answer it just answered. Pricing by `countActive − resolved.size()` therefore subtracted a repaired run twice whenever a second run was genuinely live — one corrected count below the policy bound admits a fresh fire the policy forbids (witness: overlap skip with a wedged run and a live one). Price instead off the recounted active, subtracting only the occurrences the mirror still lists blocking — in production nothing, in the unit fake (which never writes the mirror) exactly the faked answer, so the earlier witnesses hold under both shapes. 2. Resolution must key on the ASKED occurrence's run going terminal (journal vocabulary 'settled' plus the mirror's 'completed'), never on the route's bare repaired flag: the harness keeps the oldest pending wake input, which can be another occurrence's crashed turn while the asked run stays live — the flag alone would resolve it anyway. Witness: aSettledRunIsNeverSubtractedAgainWhenTheMirrorAlreadyDropsIt — a wrapped connector applies the failed mirror row inside the reconcile answer (production shape), beside a second live run; the slot 45/46th test stays refused where the old arithmetic fired. Old witnesses re-run green: the wedged run still reconciles into the very slot that asked, an unanswered reconcile still keeps its refusal, an open Turn still triggers none. * fix(managed-agent): settle the journal side of a deterministically declined wake park Audit round 1 (lane B). For a wake turn whose park the Broker cannot identify — a Shell item on a hooks/MCP-defined Session, a runtime route committed for another Session — the recovery machinery answers a typed, deterministic RecoveryDeclined, the same verdict a user Turn's takeover gets. The crashed wake turn's aftermath read every such answer as 'pending': one decline wedged the Session in an endless 500 ms retry (pump cycle, next load, reconcile_run) before any reload could classify the crash away. Now an inner decline path settles what the journal can settle anyway: the cancelled tool results are committed once (their idempotent settle is what the stop usually proves first), the checkpoint advances out of await_runtime, the consume write follows its retries, and the aftermath answers settled — the run ends failed with its execution unknown and the Session unblocks in-process. The wake session's own lease, undeliverable without its broker identity, stays the system's re-admitted kind, exactly as the takeover path leaves an undeliverable release documented at runtimeLeaseHeld, rather than pretending an infinity of retries. Witness: a shell-parked fixture wakes the decline; the aftermath answers settled, the execution proves outcome_unknown, the checkpoint leaves await_runtime, one cancelled tool_result pairs with the parked call, the journal consumes the crash. Old witnesses re-run green, including the latched-broker park settle that relies on 'pending' for every transient fault. 317/317. * fix(managed-agent): refresh-retire a definition gone under the scanner's feet, size the reconcile fan-out to the cap Audit round 1, lane A's two reconcilable minors. A reconcile that meets the chain's death — the definition retired, unknown, or its Session gone — must do what a fired answer's definitive refusal does: retire the mirror and record the slot skipped(definition_retired). Reading it as 'no answer' and keeping skipped-overlap would now stand every future slot forever, and a retired definition never frees its ledger. And the fan-out bound was a hard 5 — below any configured allow concurrency: wedged runs beyond it second a refusal the next tick could have finished this tick. Size it by the configured bound (at least two), exactly how many runs the refusal itself can still count at `settings.getConcurrency()`. Witness: a wedged minute definition meets its journal-death over a reconcile — the mirror retires, the occurrence reads skipped(definition_retired), the next tick never asks again. The earlier pins (wedged repair, unanswered reconcile, live-turn untouched, mirror-lagging math) stay green at 47/47. * docs(managed-agent): cover the definitive-4xx settlement and the audit-corrected reconcile math Lane C found decision 5 silent on the generic definitive-4xx arm the scanner shipped (typed code recorded as the skip reason; the code's name alone records it, hosted_session_blocked included), and the audit round of the same pass corrected how decision 15's reconciliation maths should read: the recount after a reconcile subtracts only what still reads blocked (the settle and the mirror row one commit together), a run resolves only by its own terminal answer (never the session-scoped repaired flag), a dead chain or Session refresh-retires the mirror as skipped(definition_retired), and the fan-out sizes itself by the configured allow bound (2 at least). * fix(managed-agent): take over the cold Broker before a parked crash's stop and release, and publish wake Shell turns under their mapped identity Round 5 review, findings 1-2. For finding 2 (the parked crash's aftermath dies against a cold Broker): the Harness and its Broker expire together, so the replacement owns an empty Session table — the status poll on the persisted READY wake session answers 404 runtime_session_not_found, the release 503 runtime_reconciliation_required, and the consume never runs, so the Session stays blocked in whatever loop its pump, next load or reconcile keeps walking. stopParkedRuntimeExecutions now adopts before any status question, as the passive takeover does — re-acquiring a READY session under this identity is idempotent server-side, and a 409 runtime_session_not_acquirable is proof the session is already released, so nothing remains to ask; the aftermath's release block adopts before releasing the same way. The cancelled tool results pick up the same bounded transcript-conflict retry the consume write carries. For finding 1 (Tool v3 automations cannot publish a wake Shell turn): the Tool turn's publisher and binding references carried the raw prompt id while prepareV3 and encodeToolInput speak the mapped wake-<sha256>, so the real ToolPublicationStore answers 'Broker execution identity conflicts' at reserve. The reference surface across prepare / publisher / /grants now mirrors the Broker's stored identities (this.broker.runtimeSessionId), with the logical turn id kept only where the checkpoint asks for it. Witnesses: a wake shell turn's publication binding mirrors wake-<sha256> across reference.sessionId/promptId with the raw id never admissible, refuting the mapped assumption; the folded park settle proves an adoption precedes every status and release on the cold surface (acquire < status, acquire < release), beside the settled crash, the released lease and the replayed failing run. Suite 318/318. * fix(managed-agent): keep the cold-Broker adoption at the wake path's own sequence, out of the shared recovery step Review round 6 (the red unit lane). stopParkedRuntimeExecutions is shared by the user-Turn cancellation takeover, which already adopts the parked session inside its own passive branch — inserting an adopt step into the shared function made that ownership double: takeover-callers hit their no-server doubles on a real fetch and died outright, and in the two 'outcome is unknown' cases the new call pre-empted the typed refusal with a transport error. The adopt obligation belongs to whichever caller starts from a Session that no harness session ever owned — the crashed wake turn's aftermath — so it now lives there alone, before the stop and before the release; the recovery function itself returns to its base shape. The cancelled tool results keep the bounded transcript-conflict retry. The lane 44/44 backs this in context with the folded cold-surface pin (318/318), whose order pins (acquire < status, acquire < release) continue to hold. * fix(managed-agent): read the role column for automation access checks after the roles migration * fix(managed-runtime): close the R3 correctness and pin-hardening findings - assertAutomationDefinition refuses a non-string goal with the typed contract error instead of a raw TypeError (R3-9) - automationTurnText and the fire route bound firedAt to the renderable Date range, answered as invalid_automation_operation (R3-14) - drop the manualOccurrenceKey export nobody consumes (R3-17) - pin the two terminal states outcome_unknown must never certify (R3-29), the definition digest pre-image byte-for-byte (R3-30), and the Legacy control sentence in full (R3-31) - resolvesTimezone matches the JDK acceptance set for the three short ids a full host-tz sweep found diverging (R3-33) - wallClock aligns to the minute before subtracting, so a sub-minute fraction no longer biases the zone offset (R3-34) - compileCron shape-refuses the atoms the contract and the Java evaluator refuse instead of coercing them with Number() (R3-35) - a fresh-command re-claim of one occurrence meets the committed run instead of appending a no-op revision (R3-36) - pin the one-transaction dispatch span (R3-37), fence the operation.replayed answer to the committing Session and journal the honored revision's authoritative ref (R3-38, R3-39) * fix(serve): close the R3 findings in the automation and wake surfaces - the publisher's foreground register compares the mapped Runtime identity on wake turns, never the raw logical id (R3-2) - an erased checkpoint verdict fails the wake park probe and the crash aftermath closed instead of answering over state nobody read (R3-6, R3-7) - the F8d prompt refusal carries hosted_turn_recovery_in_progress, the one code the coordinator outlives past its budget until the pump settles the park (decision 15) - pin the already-released adoption, the dead-writer 503, the mapped Shell owner, the resume builder's unanswered exclusion, the cold surface's acquire ordering, the reserve reference presence, the firedAt admission bound, and the mode-gated define publishing nothing (R3-20, R3-54, R3-56, R3-53, R3-5, R3-24, R3-14, R3-50) - one wake-source predicate instead of three copies (R3-25), keyed run lookups on the fire and reconcile paths (R3-26), and the quota message naming the code the route emits (R3-49) * feat(managed-agent): split the wake-park refusal code so the user Turn outlives the pump window A prompt refused because a crashed wake turn is still unsettled clears under the wake pump's own recovery channel, so the coordinator's five-retry pre-admission budget is a lifetime for a transient state. The route now answers hosted_turn_recovery_in_progress for exactly that window and the coordinator retries it without spending the shared budget; hosted_turn_recovery_required keeps the durable meaning and still meets the budget (decision 15; R3-1). * fix(serve): dedupe the merged records imports and finish the wake vocabulary union * fix(managed-agent): close the R3 findings across the automation control plane - the scanner settles skipped only for the route's definitive refusal codes; a transient session-state 4xx (hosted_session_blocked most of all) defers and re-drives under the bounded backoff (R3-11) - create() exempts its own landed definition from the capacity check, so a lost-answer retry at the 32-cap re-drives instead of being refused by the very definition it committed (R3-3) - retire() retires locally when the Session is not ACTIVE, the fallback a forever-skipped definition otherwise never meets (R3-12) - the automation relay resolves the attachment before reading the client, as doContinueManagedRuntime already requires, so an adoption landing mid-load can never strand the call on the closed instance — and the child relay takes the same order (R3-10) - the automation-run binding check and the scanner's mirror read both converge on ManagedExtensionRecords' terminal set (R3-43) - automation tunables bind with startup bounds exactly like the sibling harness settings, and the shipped YAML's flattened keys are pinned (R3-40, R3-41) - countActive, findBlockingRuns, listOccurrences and findOccurrenceView read each run's mirror state by its exact projection PK instead of a whole-table join; the mirror refresh sweep gains its (domain, created_at, record_id) index in V55 (R3-45) - the fake's definition field set is one list (R3-48) * docs(design): sync the actor-roles and automation documents with the landed surface The public surface is eleven controllers with 39 public + 24 WebShell + 24 internal routes (87 total): the overview and the per-class tallies read that, both languages (R3-22, R3-32). The 2026-10-04 automation document's H6a-era domain bullet names what H6b enabled, both languages (R3-21). * style(serve): prettier-normalize the merged wake surfaces * fix(managed-agent): close the review's three P1 findings at |
||
|
|
8c127de524
|
feat(managed-agent): H5b/H5c channel runtime for the email reference adapter (#13572)
* feat(managed-agent): H5b/H5c channel runtime for the email reference adapter Make the two channel domains executable end to end on the managed path. Inbound: a trusted adapter submits an authenticated platform event to the control plane, which dedupes it on the four-part ingress identity (the V47 route row), resolves or creates the route's Session idempotently, and asks the Harness to commit the route binding revision, the staged attachments, the input and its wake in one journal transaction; the row turns admitted only after the Harness answered, so every crash window replays to the committed admission. The embedded wake scheduler runs the input as an ordinary text turn. Outbound: a settled channel turn plans its reply as a channel_delivery record; the adapter pulls claims through the control plane, each step is a record revision the Harness commits first and the V47 ledger follows; a claim that outlives its lease settles unknown and is never resent; an explicit resend opens a new chain. The three public channel resources move from planned to partial. Both domains join the enabled list behind an adapter gate (email only) that the authority checks on every first route revision through the committed policy; a delivery binds to its committed route at the pinned revision in both languages. V52 adds the registered connections, the route catalog and the claim ledger. The email adapter gains a managed mode (qwen channel managed-email) that reuses the Legacy parsing and transport helpers and keeps its own locked state. Part of #12827 (Stage H, slices H5b and H5c). Refs #12380. * fix(managed-agent): carry the thread id in the channel client test's reply context * fix(managed-agent): close the recovery and isolation gaps found in H5 review - enforce the isolated internal listener when channels are enabled: the trusted adapter surface authenticates by the tenant header alone, so the broker refuses to start without qwen.managed-agent.internal-server.port - converge the delivery ledger with the committed record before a lease reconcile settles: a receipt whose answer was lost lands as delivered with its provider id, a committed claim moves the ledger to sending, and only a still in-flight claim settles unknown - discover owed deliveries through the extension-record projection instead of the newest 64 bindings, so a thread beyond the window is still claimed - plan the reply segment before publishing it so a long turn honors the inline resource bound, and exclude thought parts - persist a segment receipt before reporting it, replaying the persisted receipt on restart instead of a blanket unknown - clamp the upload bound to the wire base64 limit and treat a deterministic 400/403/409 refusal as permanent, never re-driving a poisoned admission * fix(managed-agent): close the H5 review round's production breakages - gate planReply on the journal turn.settled event: the production sink commits turn results there and never projects them, so record-projection gating planned nothing outside tests - reconcile replies per turn, so one unplannable turn (a re-keyed route pin) no longer aborts the loop on every open - replay a segment's persisted claim when the control plane re-offers it, instead of sending a second copy * test(managed-agent): expect V52 in the upgrade-from-V47 migration set * fix(managed-agent): close the remaining H5 review criticals - re-gate a rebuilt pending event on the current sender policy, dropping it visibly instead of driving a forbidden Session - report a reaped state lock through onCompromised and stop, like every other lock owner in the tree - add --cwd to qwen channel managed-email and thread it through channel config resolution, matching the Legacy daemon's state directory - refuse an out-of-range receipt before any revision commits - answer a settle against an already terminal chain with the committed chain instead of looping the caller on a 4xx - bound the reply context in the channel operation parser before staging could side-effect - run the channel adapter gate on every route revision, not only the opening one - give runChannelOperation the same workspace reauthorization and generation adoption as every other new-work dispatch, and translate plain daemon failures into the retryable envelope * docs(managed-agent): correct the design pairs to the shipped H5 runtime - mark H5b/H5c done in the slice table and qualify the domain-enablement statements as H5a-scoped (H5 design pair) - name the actual rebase point ( |
||
|
|
b69338b143
|
feat(managed-agent): H4b child Session runtime (#13550)
* feat(managed-agent): H4a child agent and child acceptance record contract Define the H4 delivery map as six slices and land the first: the managed-child_run body for kind child_agent and the managed-child_acceptance body, both dispatched inside the managed-child_run/child_acceptance schema version 1 records, with commit-time cross-record consistency and resource closure rules and dual-language shared fixtures. The record-body registry's static task kind becomes a function of the record so child_agent projects the existing child_agent task kind while child_acceptance projects no task. No domain is enabled for submission and no runtime path changes; enabling child_run and child_acceptance is H4b's explicit, server-first-deployed step. Refs #12380, #12827 (slice H4a) * fix(managed-agent): Tighten the child agent record contract from review Answer the three review findings on the H4a contract: the result and its terminal receipt now change only together and only in the revision that settles the run, so a half-result can never wedge the chain; the child Session identity is required once the execution proves creation (running_attached or settled), not merely forbidden before it; and a terminal child agent record freezes everything but its delivery line, so a stop request can never be added to terminal evidence. Fixes are mirrored in TypeScript and Java with new shared fixture witnesses and eight killed targeted mutants across both validators. Refs #12380, #12827 (slice H4a) * fix(managed-agent): Close the H4a real-stack verified Java parity gap The real-stack verification on the PR head found the Java child_agent validator skipped the identity checks the TypeScript one makes on childRunId, ownerScopeId and rootSessionId: bodies the writer would refuse were stored (HTTP 200), and a Session holding one could not be reopened - exactly the disagreement the server-first enablement order depends on the store rejecting. Apply the verified candidate intact: three id() calls at the end of requireChildAgent plus three shared fixture cases, closing the differential to zero in both languages. Fold in the regression-protection gap found with it: the store-level acceptance binding test (run kind, completion call, receipt digest and the launch input's resource closure) and the two authority witnesses for the receipt binding and the closure, each proven red by its matching mutant in both languages. The H4b delivery-direction observation (delivery can outrun or contradict an acceptance) is recorded as such in the design's follow-up section, in both languages; it is not blocking for this slice. Refs #12380, #12827 (slice H4a) * fix(managed-agent): Gate shell capture admission on the shell-only parser Round-2 real-stack verification found the remaining shell-only child_run consumer missed in the parser widening: LocalShellStreamResultSession proves a background Shell's start before admitting an output capture by looking up the record by the capture's executionCallId, and since H4a the widened parser would also parse a child_agent record as that proof. A child agent with a matching call id would be admitted as a proven background Shell start. It now parses with parseChildShellRun like the cli callers, and a new witness refuses a child_agent record at admission; the same-domain monitor confusion suite passes unchanged. Fold in the remaining regression-protection gaps the same review round named, one authority case and one store chain closing the settling revision's result and receipt resources, mutants J7b/T7b killed by each. the three F1 id removals JF1-JF3 and the reverse-parser witness are proven red as well. Refs #12380, #12827 (slice H4a) * fix(managed-agent): Close the H4a child-agent contract's enforcement gaps Real-stack review of #13505 (bot R1 round plus human P1/P2 round) found the child_agent validator certifying records its own design forbids, with the terminal freeze then locking every omission in permanently: - require the dispatched definition pin and the Runtime binding that hosts an attached child Session, in both languages (the successor rules make both unaddable once dispatched); - refuse Windows drive specs in workingDirectory in both languages; - read resultVersion with a finiteness guard in Java, so 1e400 refuses instead of throwing NumberFormatException; - bind a first-level child's rootSessionId to the journal's Session at commit time on both sides, and drop two successor clauses the terminal freeze made unreachable; - teach verifyWorkspaceRestore to skip child_agent records instead of misparsing them as shells, give the shell-only parser its own refusal clause, dedupe the shell stop-reason logic onto the shared helpers with the same clause order in both languages, and derive ChildRunKind from the union instead of a dead parallel spelling. The shared fixture corpora pin every new rule in both languages and the 74 pre-existing entries keep their merge-base formatting; the replay harnesses now fail loudly on an unresolvable template; six successor fixtures that never reached their named clause are restated or dropped; eleven unwitnessed validator terms gain cases; the taskKindOf probes run on real records; and the design docs correct the nine stale claims their review round measured (citations, the unknown-delivery wording, the depth-cap relationship, V48+). * feat(managed-agent): H4b child Session runtime Slice H4b of #12827 (Stage H of proposal #12380), stacked on H4a's child/acceptance record contract: the child Session runtime — the four producers of the H4a bodies, the first cross-Session dispatcher, the close cascade, and kind-selective enablement, on the server-first order. Parent side: the Agent tool joins the Hosted Workspace Shell-lane declaration set behind a per-kind gate; startChildRun commits the launch intent with the definition pin and the derived record key before any side effect; the foreground arm waits on the committed chain alone and answers from evidence, the background arm returns a task handle and takes its result through a durable notification input plus wake, with accepted delivered after the answer and consumed after the consuming turn settles — two independent, separately observable facts. Control plane: V51 lineage columns, the delivery-pending index and the relay ledger qwen_managed_child_result_relay (the reserved task-event name stays reserved); createChildSession under the parent's exact binding stamps parent/root lineage idempotently from the launch record key; the relay drives creation, attaches, copies the terminal result and receipt digest-bound, submits the acceptance, marks orphaned at a closing parent and unknown past bounded retries, never re-executing. The close cascade in SessionLifecycleCoordinator cancels non-terminal children through the ordinary close pipeline before the Harness close. Gates: child_acceptance joins the plain enabled list; child_run gains MANAGED_SESSION_ENABLED_CHILD_RUN_KINDS=['child_agent'] so H3's shell stays disabled — and the owed delivery-direction decision landing as the reverse commit-time check in both languages (delivery reaches accepted/consumed only with its acceptance record, and never returns to unknown/rejected after one). Public OpenAPI, Flyway-adjacent schema and WebShell surface do not move. Validated: core 2310/2310 and cli serve sweeps green (timing-sensitive flakes documented in .qwen/e2e-tests/managed-child-session-runtime.md, including one pre-existing failure reproduced on the base tree); managed-agent-server surefire green except one pre-existing integration failure reproduced on the base tree; 12 targeted mutations (7 TS + 5 Java) all killed; dual-language contract replays green. * fix(managed-agent): Close the H4b review round's relay, cascade and wake-consumption gaps The review round found five child-runtime gaps this commit closes: - findChildLineage returned a null row for a root Session through Stream.findFirst() and threw, breaking every Hosted Session create. - The relay binding probe read the tool-session Runtime record, which a text-only child never acquires; it now reads the physical Runtime binding that warm() creates when the Hosted tool turn is constructed, and the tool-session read path is retired with it. - The close cascade treated a child created but not yet attached as unstarted; it now resolves the child through the relay ledger before judging, and closes it through its own Session lifecycle (admitted idempotent close, owed until terminal Session status) instead of a bare daemon DELETE — child creation inherits the parent creator's workspace grant so that admission passes the creator checks. - Journal debt (stop or terminal revisions faltering) re-arms the whole close instead of being dropped, with the acceptance-answered early-out restricted to arms past delivering so the relay still owes its own mark_accepted. - The wake-turn pump treated its blocked sentinel branches as settle proof and consumed the delivered child result without a Turn; the consumption wrapper now requires a genuine settle. Terminal-classified relay rows also leave the discovery set so a grown ledger cannot starve the fleet scan, the funnel routes conflict and record refusals as typed errors instead of message matching, the turn-settle flush keeps the unconsumed remainder owed when it dies mid-commit, and the design documents carry the corrected semantics in both languages. * fix(managed-agent): pass the MariaDB-lane Java checkstyle gates The pushed H4b review fixes left a trailing-whitespace javadoc line, two imports eroded by the relay binding probe change, and an empty switch default written in a shape both checkstyle rules reject; the lane runs checkstyle:check that the local mvn test path does not. * fix(managed-agent): reconcile the H4b create→attach window, lost delivering advance, and attach-less settle The second review round proved three gaps in the first: - A child whose create answered (Session and lineage committed) while its ledger advance died was judged unstarted by the close cascade: the cascade now consults the relay ledger and then the committed lineage row, so a missing ledger id never proves not_started. - Settling such a ledger-only child committed close_scope(started=true) over a body whose childSessionId is null, which the record parsers must reject: the cascade first replays the dispatch_started/attach chain from the proven evidence — the bodys own committed dispatch facts, or the physical Runtime binding the child warms with — so the settling revision always commits over a chain the parsers accept. - An accept committed while the delivering advance was lost left the ledger watching with the answered acceptance short-circuiting every scan: answered acceptance short-circuits only arms the relay never owed, and a watching row reconciles through the same idempotent walk (replay the result, the acceptance, then the advance). * fix(managed-agent): tolerate repair journal failures and replay committed child results The review round on the create-attach reconciliation found two more: - The repair ops' refusal/transport-ambiguous throws are plain RuntimeExceptions (DaemonHttpException, MutationOutcomeUnknownException), which escaped the IllegalStateException guard and skipped the child's own physical close — the reconstruction's journal debt no longer preempts the physical effect. - A commit_result whose acceptance reply was lost minted a second terminal revision over frozen result references: settleCompleted now answers the committed copies after byte-equality with the published resources, so a replay returns the original result instead of a revision the terminal rule must refuse. * fix(managed-agent): expect the hook-listed Agent tool in the hosted tool-turn E2E driver H4b advertises the Agent tool on depth-0 Shell-lane Sessions behind the child kind gate, so a shell-profile fake-model assertion pinning exactly [edit, monitor, read_file, run_shell_command, write_file] legitimately now reads agent first; the other lanes keep their exact vocabulary as designed (files-driver unchanged). * fix(managed-agent): commit tool-arm consumption only after durable Turn settlement The tool-arm consumption flush fired before the turn_result commit: a crash between them, or a turn-result write that keeps failing through the outer retry, left child records durably consumed while the consuming Turn never settled. The flush now runs only after the Turn settlement commits — the wake turn's requirement applied to the tool arm too; and a mid-flush death still keeps the remainder owed in the queue, so the recovery drive can finish each committed side. * fix(managed-agent): wire the real H4b stack end to end A real-stack verification rig of head proved three blockers and three follow-ups, every one confined to seams mocks could not reach: - The relay got a null Runtime Broker in the real Spring app: the owned service never became a bean, so scan never walked a single pending row. Its owner now exposes service() and the configuration registers it with destroyMethod = "" (lifecycle stays with the Broker), and the relay tolerates missing wiring as a retry instead of a null drive. - terminalResultText filtered part_type = 'text' while the only writer stores assistant text as 'output_text'; the query and its fixture now agree with the written vocabulary. - The sent-arm notification escaped its full text into the bundled input, so an XML-hostile 20 KiB result rejected accept at the inline bound: the notification now carries the design's bounded summary — escape-then-bounded below the inline cap with a marker — and full bytes stay on the acceptance record. Its <task-id> is the task_ id the launch itself returned. - A foreground child + any tool deadlocked on the shared Workspace mount with its waiting parent: the mount is taken only for a batch with a non-agent tool, never held across an agent-only wait. - A done child kept an ACTIVE Session and its worker forever: the relay now admits the child idempotent lifecycle close (shared with the close cascade through a new ChildLifecycleAdmissions component). - A child still RUNNING no longer eats the attempt budget — deferred without attempt++, scheduling is not failure — and the discovery set resurfaces delivering ledger rows whose record raced past accepting, so their owed mark_accepted/classify-done step always completes. * fix(managed-agent): close the round-3 seams the second rig and bot proved The re-verified real stack and the bot's CHANGES_REQUESTED round found the rest of the same family: - The bundled notification still wedged: the bound measured the XML text, but publication JSON-serializes the envelope — and the wake turn's own message envelope would blow next. Bound now: packaged JSON bytes against a 48 KiB budget (the design's summary, never the whole answer), computed over the multiline-friendly stripping that keeps newlines instead of gluing lines. - Refuse a foreground agent call when the parent turn already holds the Workspace mount or the batch has a non-agent sibling — the deadlock cannot start rather than burn to the deadline. - A redriven (recovery-continuation) turn consuming child results now drains the same per-id consumption flush; previously its declare-only path would silently pass the queue to the next unrelated turn. - Discovery resurfaces also the watching arm behind consumed/accepted deliveries, and deferral/leased rows no longer occupy the bounded fleet window ahead of real due work. - classify can't be rewritten by a stale claimant (owner guarded in the WHERE), and the Runtime binding probe answers only live READY rows — newest READY, not the lexical max across inert generations. - A done child's close admission runs before classify(done): the row stays owed and retried until admitted, so a falter keeps the close recoverable rather than an invisible ACTIVE orphan. - Gradle-proof witnesses everywhere the round was blind: RuntimeBrokerConfigurationTest pins the bean's existence, and RuntimeBrokerServiceBindingReadTest pins the READY-only filtering. * fix(managed-agent): answer the bot's full round review, first batch of fills Review round answered inline by thread: - Replay halves of the funnel locked into the one serialization: settleCompleted (guard + publications + commit) and accept (check, notification, commit) can no longer mint on a racing re-drive. - Notification text: multiline strip with code-point slices (no glued lines, no unpaired surrogates), bounded by packaged JSON bytes under a 48 KiB budget per the design's summary shape. - Foreground agent already refused while the mount is held or the batch shares with a non-agent tool — deadlock never enters v1. - Discovery pulls the watching arm when consumed ran ahead; deferred and leased rows no longer squat the bounded window; classify is owner- guarded; pending discovery also serves the watching arm behind a committed acceptance. - Binding probe answers only READY (newest READY), never the lexical-max across inert generations. - Tool-arm completion witness pins the no-notification shape; coordinator cascade test now asserts ordered sequences and the derived dispatchId. - New witnesses: discovery columns, consumed pre/post-acceptance halved, child_agent wake intake, config bean pins, binding-read, authority gate commit, multiline/JSON size bounds. - RuntimeBrokerService delete-safe javadoc row and creation-key delegation; docs: six ops belong to managed-child-operations, decision 5 sent-arm pinned as the bounded summary + complete acceptance, and the plan says the reverse gate is pinned at authority/store level in both languages. * fix(managed-agent): refuse a malformed accept-notification as 400 Present-but-wrong-shape notification values were silently dropped by `object()`, so an accept op arrived with nothing. A non-object or a missing-description notification is now a typed refusal instead of a silent no-op. * fix(managed-agent): expect V51 in the workspace migration golden list Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(managed-agent): answer the round-4 review and the R1-5/R1-9 recovery gaps Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(managed-agent): pin the child terminal-result query's two decisions (R1-90) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(managed-agent): recheck mount ownership after hook side effects and settle the fixture wake before close Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(managed-agent): translate the oversized launch envelope into its byte_limit refusal (R1-8) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(managed-agent): close the R1-round's deferred Criticals and pin their regressions Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(managed-agent): fire PostToolUse for admitted agent launches (R1-62) * fix(managed-agent): keep the give-up's close-and-settle debt alive and read durable dispatch evidence on resume Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(managed-agent): renumber the H4b migration to V52 past main's V51 lifecycle Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk-java): give the unconfirmed lifecycle-detach heartbeat room on busy runners Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(managed-agent): reconcile attach from the parent record at give-up and admit claimed child cleanup past the lifecycle fence Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(managed-agent): give the fence its claim kind and keep stale authority out of the client Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(managed-agent): authorize claimed child cleanup through the lifecycle commit gate Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(managed-agent): settle Shell-lane parents and children without Workspace close SessionLifecycleService admits protocol 1 only for the Files profile; the Shell lane keeps the protocol-0 close whose child cascade passes ordinary authorization, so a H4b parent close/delete no longer ends RECOVERY_BLOCKED workspace_lifecycle_profile_unavailable. ChildResultRelay no longer holds a parent's settlement hostage to a close the host cannot dispatch: without Workspace-close support the relay still settles failed and over-bound child records and retires the ledger row instead of retrying forever. The harness-session route test now pins the lifecycle authority stamped on the Store client at verb-commit time. * fix(managed-agent): retain the child-close obligation as a discoverable close debt On a host that cannot close Workspace Sessions at all, retiring the relay row alongside the parent settlement discarded the only durable record that still owed the child Session its close: the child stayed ACTIVE with no ledger scan and no close cascade able to find it again, even after close capability returned. The settlement now still commits on time, but the row parks as close_debt — claimed, named by the child id, due on the heartbeat, and surfaced by the discovery page's cancelled arm — until a capable scan admits the close and only then retires. A parked debt outlives the orphaned early-out and never re-enters the give-up chain, whose settle-and-classify already happened for this row. * fix(managed-agent): renumber the child lineage relay migration to V53 #13163 took V52 for the mutation-attempt sequence while the branch was in flight, so the merged tree carried two V52 migrations and Flyway refused every context. The golden-list IT now expects 48-53 and passes against a live MySQL on the merged migrations; the module's 1439 tests pass with the renumbered layout. * fix(managed-agent): reconcile the close obligation before orphaned classification The settlement of a child run can be committed while its close_debt ledger write never lands — a crash or a lost reply between them. If the parent then closes or deletes before the next scan, the settled task is already invisible to the close cascade's live-scope walk, and the orphaned classification erased the ledger row that was the only durable holder of the owed close: child ACTIVE, zero CLOSE operations, zero future discovery. The non-ACTIVE-parent branch now reconciles before it classifies: a settled delivery whose child Session still stands (row id or committed lineage, short of CLOSED/DELETED) parks as close_debt instead and discharges through the ordinary admission. Unsettled rows classify orphaned exactly as before — the cascade owned their stop. * fix(managed-agent): read the close capability once in the give-up chain The admission order and the retention flag each re-read closeSupported(); a TOCTOU flip between the two could mark the debt settled without ever admitting the close. One cached read now feeds both. * test(managed-agent): pin the give-up chain to a single capability read Sequencing closeSupported() true-then-false must still admit the close and call hte accessor exactly once — the TOCTOU window between a double read is what lost the debt silently in the first place. * docs(managed-agent): renumber the design's migration cites to V53 and record the close debt The renumbers (V51 took main's lifecycle, V52 #13163's mutation attempt) left both language versions citing a migration id that no longer exists, and the close-retention machinery added since the doc landed had no ledger description. Both versions stay synchronized: V53 everywhere plus the close_debt / orphan-reconciliation paragraph in decision 8. * fix(managed-agent): retain the close obligation at task settlement, from every intermediate ledger state Task settlement leads delivery settlement: the run record settles and projects task completed while delivery still says accepting, so the close cascade's live-scope walk never sees it — the retention predicate must read task-settled evidence, not terminal delivery alone. The discovery page's settled arm now admits every non-terminal ledger state too: a give-up whose settle committed but whose close_debt write failed sat in binding, outside the old arm-2 list, permanently undiscoverable even before a parent close. Arm 2 now mirrors arm 1's terminal-only exclusion; the in-flight work sets stay disjoint by delivery, and the due/lease predicates still own eligibility. * fix(managed-agent): never re-enter the walk for a settled-cancelled record Widening arm 2 to every non-terminal ledger state made the settled arm discover a creating/binding row whose create-or-fail reply or terminal classification write was lost. On the next scan work() replayed the record into create() and actually launched a child already recorded as creation_failed / not_started_proven. The walk now reconciles such rows instead: a standing child owes exactly its close (parked close_debt, ordinary discharge), and with none the row classifies unknown. The settled arm keeps reaching them; only the creation/execution arms close. * fix(managed-agent): keep retained close debts from starving new child work On a host that cannot close Workspace Sessions, every finished child parks a close_debt row due again every five seconds, and the discovery page ordered by created_at always served those oldest rows first — about 150-200 delegations in, the freshest launch is never reached and no new child Session is ever created (real-stack evidence on the macOS rig). Breaking the starvation breaks nothing of the retention: due debts now sort after every other owed row on the page, and a host that cannot discharge a debt at all revisits it every 300 seconds instead of every heartbeat — nothing could land before a restart returns the capability, and a capable scan still discharges on its very next visit. * fix(managed-agent): fence relay creation recovery against settled evidence at every seam The same lost-write interruption can hide behind three seams, and each authorized the wrong outcome on its own snapshot: - The claimed walk read the discovery page's captured delivery. A record settled between the page and the claim retired or relaunched on the stale snapshot; retirement authorization now re-reads the committed projection (falling back to the page only when the row is gone). - A stalled creator whose relay lease lapsed minted the Session anyway after another worker's give-up committed creation_failed and retired: the creation admission never read the run's terminal verdict. It now fences that mint inside its own transaction with FOR UPDATE on the run's task line, so a settled record refuses and unanswered replay still answers. - A give-up with a lost relay session id resolved no attach and read no lineage: it claimed creation_failed while a real child ran. The child now resolves from the committed lineage before the proof, and a vetoed attach replay consults the child's own committed Turn before started=false may be claimed. * fix(managed-agent): never admit children queued behind a cancelled wait A batch whose foreground wait was abandoned by cancellation returned control to the batch loop, which then admitted every agent call queued behind it — work the cancelled turn never started, including a successful 'started in the background' answer. The admission gate now checks the abort signal before describing, bounding or stamping anything; keeping an already-launched child running stays documented. * fix(managed-agent): close the launch-admission edges codex flagged behind the quota line Three adjacent admission edges all authorized against the wrong party: - Quotas counted the replayed child: a re-driven batch whose siblings filled the active run quota got count_limit for the launch it was already running. The record lookup now precedes the quota verdict, and children.admit's replay identity answers as before. - The background workaround was recommended where it deadlocks: a Session-owned mount (Hook catalog or MCP owner) survives the Turn and releases only at Session close, so a background child launched under it parks on workspace_busy. The background arm refuses there, and only turn-sourced holds keep the foreground hint. - A wake turn's id embeds its commissioning run id, so chained background launches grew one suffix per hop into the 128-char lineage bound by the third hop. The launch key from a wake turn is now a sha256-truncated digest of the same stable name — replay deterministic, hop-bounded, and lineage-parser-legal at every hop. * fix(managed-agent): never let a verdict race past a still-committing settlement Two seams in the same family both read pre-collapse evidence and then acted durably on it: - Between the walk's read of the projection and the orphaned classification commit, another worker's settlement can still land — and the classification would orphan the child that settlement just made owable. The verdict now locks the record row FOR UPDATE inside one transaction for the re-read: losing that race only says the truth settled, and exactly one fresh evaluation assigns the close debt instead of retirement. - A give-up's verdict committed on the no-child proof while the stalled creator resumed and minted between the proof and the failure commit. At the commit seam, the lineage is re-read: new child evidence arrives after the proof owes one more bounded wait, never the confidently wrong pairing. * fix(runtime-broker): read every page for the newest READY binding findLatestBindingByHarnessSession returned from inside the first page that held any READY row (and advanced the keyset cursor only when it held none), so a greater-id READY binding on a later page was never considered — the live Runtime identity for a child's first controlled binding could pin a stale generation on generation-lite bindings. The whole bucket now traverses, keeping the greatest READY id seen, and returns after the traversal ends. * fix(managed-agent): retention defaults on the standing child, never the racing verdict A classification now orphaned only children that no longer stand — whichever side wins the settle-vs-verdict race, a child that still owns its Session keeps its one discoverable holder, ending the orphan-burial shape in both directions at once (forward settle-then- verdict and inverse verdict-then-settle). The atomic classify-when-unsettled from R20 folded away in favour of the simpler default; orphaned remains for the child who genuinely stands no more. * fix(managed-agent): stamp out three launch-side certification gaps codex pinned - A non-plain-object lineage field on session create now knows it: arrays (which object() maps to null) no longer stand in for accepted ancestry; only absent/null lineage is root. Anything else is the debuggable 400 invalid_hosted_lineage, never a 200 root that carries a malformed ancestry silently. - The restarted-wake-turn recovery probe asks the durable record about child dispatch under the same collapsed key the launcher wrote — acceptChildAgent's wake-turn key derivation is now shared with the PostToolUse re-query (sha256-truncated digest of identities), so resume finds it instead of losing ded though no test pinned its wake shape before. - A lifecycle claim stamped by the children/operations middleware now restores on EVERY exit of the route — including the early-refusal arms (invalid operation, unavailable profile, blocked session) that previously ran before the try and leaked the claim onto the shared store client for the rest of the process where a future claim's generations match no longer comparison. * fix(managed-agent): prove close debt by fact and address the fence by key Two avoidance-class truths respected: - A close debt whose child no longer stands is discharged by fact, not retried by suspicion — dischargeCloseDebt now re-reads the child's status before it admits, so an already-closed/deleted child never faces a permanently-refused admission, and an unproven-null read does not discharge against an unconfirmed child that we know exists. Default mock fixtures now answer ACTIVE for a child whose recovery would need it. - The child-creation terminal-truth fence addresses the parent's real launch row by primary key (session scope + record key, the same key the idempotent admission derives), so its FOR UPDATE never becomes a cross-tenant table scan against MySQL's default REPEATABLE READ. * fix(managed-agent): settle close-road blockers by evidence, not retry or wire-code faith Three segments, each adjudicated prior to code: - On a host that cannot close Workspaces, the close cascade's child close admission is fake-owed, and the delivery taxonomy gets a plain IllegalStateException it cannot file — the parent retries forever under an unnameable retry instead of the typed blocked code it already uses when the cascade had nothing to do. On such a host the admission is simply skipped, and the parent close proceeds into that typed gate. - A lineage-named child with no body facts, no ledger creation key and no READY binding has no replay source: that evidence settles as creation-never-attached with started: false once, not an eternally re-armed debt on empty proof. - A give-up's creation_failed verdict is now proved off the record's own transition field: intent (legally holding no attach or past dispatch successors) is answered never-started were nothing physical contradicts; dispatch_started or past is answered started; a physically-ran child with an intent record whose binding cannot name its dispatch answer gets deferred instead of invented verdicts. * fix(managed-agent): let a never-started verdict name its minted Session The create→attach window leaves exactly one durable fact — the minted child Session's lineage — and the terminal revision that retires such a run could never record it: the validators required the Session to stay null until a dispatch admitted it, to have a Runtime binding, and for creation_failed in particular to be anonymous. A retired run then left its mint behind with nothing able to discover it again. The contract now lets a not_started_proven settlement carry childSessionId with no binding — bounded to that one terminal, the only state with no dispatch to host from. The fail/close_scope operations accept an optional childSessionId, the builders restate the same id replay-safely and refuse a rename with child_operation_record, and the shared fixtures pin the newly-lawful shapes in both languages. Pins: core record/operation suites (190) replay the amended fixtures and the naming/rename rules; the harness-session route pin drives the wire contract end to end (named 202, replay 202, rename 409, malformed 400). Prettier, ESLint and tsc clean in cli and core. * fix(managed-agent): arbitrate terminal verdicts against the mint at commit R22's give-up wedge: executionState read the runtime_state projection (unbound/provisioning/ready) while the caller compared execution enum values, so intent and dispatch-started rows never matched — every row looked started, the refused started verdicts repeated forever, and the owed work never retired. The proof now reads the committed record body through its inline resource; an unreadable body owes a bounded retry, never a guessed verdict, and a self-proven not_started_proven execution pairs the unstarted verdict directly. R21's inverse race (a give-up verdict evaluated over an empty lineage, then the mint lands) closes at commit time: ingestion of any not_started_proven terminal revision locks the run's own row FOR UPDATE — the same row the creation fence reads — and requires the verdict to name exactly the Session the lineage proves minted, or refuses the commit. The refused verdict defers the relay, whose retry re-reads the lineage and names the child; every give-up verdict now names its child whenever the child is known. Mutation witnesses, all reverted byte-identical afterwards: a runtime_state read fails the execution-body pin (an intent fixture answers unbound); a no-arbitration gate fails the two refusal pins and passes the two admission pins; dropping the verdict's naming fails the relay's intent-family naming pin. Suites: relay 36/36, relay store 13/13 (new execution-body pin and the never-started pairing), verdict-reconcile decision table 4/4, creation fence 3/3, and the full managed-agent-server verify green (checkstyle, SpotBugs, 117 suites). * fix(managed-agent): bind the close cascade's started call to any-state evidence A creation key is an attempt id, never a start, and a READY binding's absence says nothing — R22 reproduced both halves at this head: the cascade wedged a parent in CLOSING behind a ledger-known child whose binding had retired (the repair demanded warmth the retired row could never offer again), and it committed not_started_proven over a child whose own Turn had durably completed. A start is now proven by the body's committed dispatch facts, by a binding row in ANY state (the new findLatestBindingByHarnessSessionAnyState — a retired RELEASED/LOST row still proves its dispatch and names the repair's identity, while the relay's repair-for-warmth read stays READY-only), or by the child Session's own durable Turn. With none of the three, the never-started pairing settles started:false and names the Session the lineage minted; with any, the repair rebuilds dispatch and attach from that evidence and the settlement commits started:true. The READY-read Javadoc is also reworded to name the rule the method implements (the greatest READY binding id across the full traversal; generation is not consulted), closing the R1-16 follow-up note that the old comment argued against the rule. Mutation witness, reverted byte-identical afterwards: dropping the binding/Turn evidence from the started call fails both new cascade pins with the exact R22 shape (started=false over proven execution). Suites: coordinator 11/11 (two new reproductions plus the named unstarted baseline), broker binding-read 2/2, and the managed-agent-server verify green with the broker chain reinstalled. Design amended in both languages. * fix(managed-agent): close the arbitration on the production topology, the retired binding, and the pre-admission Turn R23 reran the preserved schedules on the arbitration head and broke each side of it in one class apiece: - The verdict/mint seam reopened on MySQL 8.0.46 REPEATABLE READ: the committing transaction's snapshot was established by its ordinary reads long before the verdict's row lock, so a plain lineage SELECT missed a mint committed after that snapshot — the unnamed never-started verdict still committed, exactly the interleave the seam exists for. The lineage read now runs FOR UPDATE as well: a locking read always sees the latest committed data on either InnoDB default, and takes the same extension-then-session lock order the mint uses. Witness on live MariaDB REPEATABLE READ with two connections: a plain read goes RED ("Expecting code to raise a throwable" — the gate accepted the mint- after-snapshot verdict), the locking read stays green; the H2 decision table is unchanged. The temporary MariaDB used for the witness is torn down after the run. - The give-up's physical-child repair required READY, so a child whose binding retired (RELEASED) before the record recovered wedged the attempt budget behind an answer that could never come — no failure, no close admission, no settlement. Terminal reconciliation needs the historical dispatch identity, not warmth, so the repair replays from the any-state read too. The relay-side pin drives dispatch/attach/fail off a retired row whose READY answer is empty; its RED mutation witness (READY-only lookup) leaves the same row parked in binding, exactly the repro's shape. - A Turn no longer proves a start by existing: only its G3 dispatch pair (submission mark or harness epoch) does, mirroring the cancellation store's own predicate. A Turn the coordinator failed before warm-up or submission is a pre-admission failure and settles the named never-started pairing instead of sending the repair after a binding that never existed — R23's parent-CLOSING wedge, pinned in the relay (creation_failed settles named, no repair attempt) and the cascade (close_scope started:false named, parent completes). The RED mutation witness for both qualifications (dropping the dispatch pair) wedges all three pins exactly as reproduced. All mutation witnesses were reverted byte-identical (sha256/cmp checked). Suites: relay 38/38, relay store 13/13, coordinator 12/12, gate 4/4, and the full managed-agent-server verify green. Design amended in both languages. * fix(managed-agent): fence the never-started verdict against runnable Turns, and keep its refusal rollbackable R24's two probes held against the arbitration head, both in fresh code from this branch: - A live ACCEPTED child Turn waiting for its coordinator shares the undispatched shape with the terminal pre-admission failure, so the give-up's new dispatch-pair qualification minted creation_failed over a Turn the coordinator then claimed, submitted and completed (the close-incapable host has no admission guard to catch it). The relay now defers bounded on any Turn that neither dispatched nor terminally failed pre-admission; only the terminal undispatched half settles the named never-started pairing. Pin: an enqueued ACCEPTED Turn sends nothing and parks at binding until the admission settles FAILED, when the named verdict commits; its mutation witness (deducting the qualification) fires the verdict at once, exactly the reproduction. The close cascade needed no sibling branch — the active-Turn close admission already continues past the same window there, and the terminal truth settles on the next walk (verified at coordinator 12/12 without touching its flow). - The gate's 409 reached the Hosted writer as a generic record rejection, which the HTTP client's refusal classification did not recognize: ManagedSessionAuthority latched writeFailure and the relay's corrected named retry could never commit on the resident parent. The gate now answers its own child_run_lineage_minted code, and the client maps it to ManagedSessionCommitRejectedError — the rollbackable non-commit class the authority rethrows without the latch. Pinned at all three levels: H2 decision table and the MySQL IT assert the exact wire code, and the TS client suite drives a one-shot 409 into a typed rejection (writesStopped false) followed by the corrected retry committing (two attempts, one landed); its mutation witness (mapping removed) rethrows a plain HttpError. Witnesses were reverted byte-identical (sha256/cmp). Suites: relay 39/39, store 13/13, coordinator 12/12, gate 4/4, TS http store 65/65, full managed-agent-server verify green. Design amended in both languages. * fix(managed-agent): treat the withdrawn submission mark as evidence-neutral, never as non-admission proof R25's probe held against the intent reconciliation: G3's withdrawSubmissionAttempted after a lost admission reply leaves the Turn CANCELLED with neither dispatch mark nor epoch — the identical field shape as a genuine pre-admission failure — while the historical binding (READY → retired) remains. The terminal-shape check certified that as never-started, converting an unanswered admission into a proved non-admission: the relay then committed creation_failed over a child whose admission outcome nobody had disproved. The rule is now ordered by what can actually prove the verdict. An outstanding Turn (live, neither dispatched nor terminal pre-admission) always owes the bounded defer; the proven pair or the historical binding row takes the dispatch replay with the same identity — a terminal status never upgrades G3's reset to a proof. Only their joint absence plus a terminal pre-admission failure settles the named never-started pairing. The legacy space closes the same way it always did: no Turn, no binding, no pair. Pin: a CANCELLED Turn with no marks drives the dispatch/attach replay from the binding's identity and settles child_failed named, never creation_failed. Mutation witness (dropping the binding from the dispatch-evidence disjunction) fires the never-started verdict in the exact repro shape and is reverted byte-identical afterwards. Suites: relay 40/40, store 13/13, coordinator 12/12, gate 4/4, full managed-agent-server verify green. Design amended in both languages. * fix(managed-agent): renumber the lineage relay migration to V54 after main's V52/V53 The uniqueness gate flags the duplicate version: main landed V53__managed_workspace_roles.sql after the last incorporation, so the relay's V53__managed_child_lineage_relay.sql collides with it. Same remedy as the V52 collision: the ledger moves to V54, its two code comments and the migration-rank golden list (WorkspaceMigrationMySqlIT, now "48".."54") move with it, and both design languages name V54 where they named V53. Verified green at full depth: runtime-broker + qwencode reinstalled chain-first (the shared ~/.m2 alpha jar had been overwritten by a sibling worktree's main build, which lacked the any-state read), the stale target/classes copy of the old V53 file removed (Maven's incremental resources copy otherwise ships it alongside the rename and re-applies the whole ledger as a duplicate version), and the full managed-agent-server verify passes with 117 suites, no failures. --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
d735e20f21
|
test(managed-agent): tolerate scheduling slack in the detach heartbeat window (#13658)
lifecycleDetachStopsHeartbeatOnlyAfterConfirmedAbsence asserted that two heartbeats arrive within 200ms after an unconfirmed detach. On loaded macos-latest runners the 20ms fixed-delay heartbeat chain can stall past that window, so the continuation side flakes while the stopped side keeps passing. Keep the 200ms no-heartbeat window for confirmed detach and give the continuation side 5s. |
||
|
|
fe4d4e345c
|
feat(managed-agent): add reliable ACTIVE Workspace deletion (L3) (#13354)
Some checks failed
pnpm store producer / Save pnpm store (push) Has been cancelled
pnpm Worktree Smoke / Install (macos-latest) (push) Has been cancelled
pnpm Worktree Smoke / Install (ubuntu-latest) (push) Has been cancelled
Verify Landlock helper / x64 (push) Has been cancelled
pnpm Worktree Smoke / Install (windows-latest) (push) Has been cancelled
Verify Landlock helper / Native Linux CLI boundary (push) Has been cancelled
Verify Landlock helper / Native Windows settings and streams (push) Has been cancelled
Verify Landlock helper / arm64 (push) Has been cancelled
Qwen Code CI / Integration Tests (no-AK, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (ubuntu-22.04) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (windows-2022) (push) Blocked by required conditions
E2E Tests / Build for E2E (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 1/2 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 2/2 (push) Blocked by required conditions
E2E Tests / E2E Interactive - OpenTUI renderer (bun) (push) Blocked by required conditions
E2E Tests / channel-plugin E2E (nightly) (push) Blocked by required conditions
E2E Tests / cron-interactive E2E (nightly) (push) Blocked by required conditions
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Flyway migration version uniqueness (push) Waiting to run
SDK Java / Runtime Broker and Managed Agent MariaDB / Java 21 (push) Waiting to run
SDK Java / Hosted process fault gates / MySQL 8.4 / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* feat(managed-agent): add reliable ACTIVE Workspace deletion * fix(managed-agent): align lifecycle CI fixtures * fix(hosted): authorize detach after durable lifecycle recovery (#13354) * fix(hosted): scope lifecycle fence locks and authorization errors * fix(managed-agent): resolve L3 migration version collision (#13354) * fix(managed-agent): address PR review feedback (#13354) * fix(managed-agent): address PR CI fixture failures (#13354) * codex: address PR review feedback (#13354) * codex: address PR review feedback (#13354) * fix(managed-agent): preserve main credential and migration contracts (#13354) * codex: address PR review feedback (#13354) * fix(managed): restore original lifecycle Hook owner before dispatch * codex: address PR review feedback (#13354) * test(managed-agent): repair lifecycle receipt journal fixture * fix(managed-agent): fail closed on stale lifecycle capability client * fix(managed-agent): align native lifecycle test contracts * codex: address PR review feedback (#13354) --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
fcc366427d
|
test(managed-agent): harden pinning witnesses and add renewal-arm pinning witness (#13388 follow-up) (#13401)
* test(managed-agent): harden pinning witnesses and add renewal-arm pinning witness (#13388 follow-up)
The #13388 pinning witnesses sized their caller fleets with
ForkJoinPool.getCommonPoolParallelism(), which drifts from the real
virtual-thread carrier count whenever jdk.virtualThreadScheduler.parallelism
is overridden — the witness then parks fewer callers than carriers and the
red state can no longer form. Read the carrier count from the same property
the virtual-thread scheduler reads instead.
Also add the BindingRenewal-arm sibling witness: carriers+2 virtual threads
each warm their own durable binding and park inside the renewal guard at a
latched first resource-handle write, while an unrelated virtual-thread probe
must still run. Restoring the synchronized BindingRenewal turns it red
(probe starves, callers never all arrive) while the SessionContext-arm
witness stays green, and drop the no-op assumeTrue(true, ...) triage nit
from BrokerVirtualThreadPinningTest.
Refs #13333
* test(managed-agent): read carrier count base-10 and land the renewal witness on its probe assertion (#13401)
* chore(managed-agent): re-arm verification after clearing stale JaCoCo lint residue (#13401)
The previous commit was rejected at npm run lint: its own mvn clean test
runs left gitignored JaCoCo HTML reports (jacoco-resources/*.js) under
packages/sdk-java/{runtime-broker,qwencode}/target, and the repo-wide
eslint sweep (flat config does not honor .gitignore) linted them — 278
errors, all generated files. No committed file caused the failure and no
in-footprint change can prevent it: the durable one-line ignore for Maven
target/ output belongs in the workspace-root eslint.config.js next to the
existing packages/desktop/src-tauri/target/** entry, which the round
boundary reserves for maintainers. This empty commit preserves the
rejected fix unchanged and re-arms deterministic verification on a tree
with the build residue removed (mvn clean equivalent); npm run build,
npm run typecheck, and npm run lint all pass on it.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(managed-agent): carrier-size the session witness latch and pin the base-10 carrier read (#13401)
* chore(managed-agent): re-arm verification after clearing regenerated JaCoCo lint residue (#13401)
The previous commit was rejected at npm run lint: its own mvn test runs
regenerated gitignored JaCoCo HTML reports (jacoco-resources/*.js) under
packages/sdk-java/qwencode/target, and the repo-wide eslint sweep (flat
config does not honor .gitignore) linted them — 139 errors, all in
generated files. No committed file caused the failure; the committed
fixes are preserved unchanged. No in-footprint change can prevent the
residue: the report goal is bound to the test phase by the module poms,
and the durable one-line ignore for Maven target/ output belongs in the
workspace-root eslint.config.js next to the existing
packages/desktop/src-tauri/target/** entry, which the round boundary
reserves for maintainers. This empty commit re-arms deterministic
verification on a tree with the build residue removed; npm run build,
npm run typecheck, and npm run lint all pass on it.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(managed-agent): align carrier reads with the JDK and pin the scheduler lane (#13401)
Round-3 review follow-up on the pinning witnesses:
- CarrierCount and the qwencode twin claimed to read the scheduler
property "the way the JDK does" while trimming, catching
NumberFormatException, and clamping — three things the JDK's bare
Integer.parseInt in VirtualThread.createDefaultScheduler never does
(verified against javap on OpenJDK 21.0.12.1). On any malformed value
the JVM dies at scheduler init, so the lenient branches were
unreachable in any JVM that can run a witness. Both copies now read
exactly like the JDK; the trimsBeforeParsing and "abc" assertions
become assertThrows(NumberFormatException) pins of the measured
behavior.
- The carrier-sized arrival latch opens with two callers short, and no
surviving assertion noticed a caller blocked before the guarded call
(measured: wedged suite green in 61 s). Both broker witnesses now
assert every caller finished after the join, restoring the lost proof
(same wedge red in 31 s).
- Nothing in CI set jdk.virtualThreadScheduler.parallelism, so the
carrier sizing was unobservable and a revert stayed green. A second
surefire execution now re-runs the three pinning witnesses with
-Djdk.virtualThreadScheduler.parallelism=4 on the fork argLine
(@{argLine} keeps the JaCoCo agent; combine.self=override keeps the
fault-gates profile's groups filter from emptying the lane). Reverting
the sizing reds both broker witnesses there in 60 s (waiting=6); head
passes in ~1 s each.
Mutation probes: Integer.decode swap reds both base-10 pins
(expected: <100> but was: <64>); semaphore wedge reds on the new
liveness assertion and greens without it; sizing revert reds the new
lane. All reverted; the committed tree is green everywhere.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(managed-agent): gate the pinned-scheduler witness lane on no -Dtest filter (#13401)
* chore(sdk-java): re-present the tree on a lint-clean checkout (#13401)
Same-run verification repair for
|
||
|
|
4970bfa172
|
feat(managed-agent): adopt the next Hosted Harness generation (G3) (#13174)
* feat(managed-agent): adopt the next Hosted Harness generation (G3) A Hosted Session no longer dies with the Harness process generation that first served it (#12952 G3, Steps 1+2). On a generation signal the control plane renegotiates once through the connector instead of failing every bound Session until its own restart; pending Turns re-attach through the takeover load as their retries come due. Takeover refusals split into a typed terminal decline (deterministic in the journal) and the existing retriable code; the takeover load is idempotent against a lost reply and gets its own timeout; negotiation requires the journal-contract feature token so a rolled-back Harness is refused once; a marked-but-never-admitted Turn withdraws its submission mark, backstopped by journal commandId idempotency. The failover runner gains a Harness-only restart arm on all three scenarios (harness_boot_id moves with no Java restart) and a SIGSTOP/SIGCONT frozen former-owner fencing arm, wired into hosted-harness-mysql together with the previously local-only basic mode. Design docs included in English and Chinese. * fix(managed-agent): tolerate the bootstrap 404 of a restarting Hosted Harness The serve delegating app answers every routed path with a bare pre-contract 404 while its runtime is still starting. The Harness-restart arms raced that window: a prompt submitted milliseconds after the new process bound the port could meet the bare 404, which the client read as a generation contract violation and ended the Turn as hosted_harness_protocol_error without any retry. A missing boot header on 404 now maps to a transient transport error so the coordinator retries through the window, and the failover runner waits on deep health (?deep=1) before driving turns into a restarted Harness. Terminal turn failures now also log their underlying exception. * fix(managed-agent): freeze only the Harness writer in the former-owner fencing arm SIGSTOPping both original owners left the original Spring's workspace binding unreclaimable: the reclaim proves death from /proc liveness, and a stopped JVM still reads as alive there, so the replacement's reconcile timed out (runtime_broker_reconcile_timeout) and the Turn never completed. The arm now stops only the original Harness (reclaiming its workspace binding requires the Spring to die anyway — the continuation mode's crashProcess), completes the Turn with a full replacement, then wakes the frozen writer and asserts the journal, transcript, and session binding are untouched. Design D7 updated in both languages with the discovered boundary: resource-level takeover of a surviving owner is structurally out of reach today, which is exactly what this issue's sequential-generation scope excludes. * ci: retrigger sdk-java workflow after event delivery stall * fix(managed-agent): pin the fencing proof on writer identity, not head revision The first merge-base run of the freeze arm failed its own gate: after waking the frozen Harness the journal head moved (rev 30 -> 45) while the writer generation, boot binding, transcript, and terminal count all held. The moved revisions were the replacement's own legal churn, not old-writer mutation, so the head-equality assertion conflated the two. The exit-check claim is identity: no transaction out of the old writer generation after it wakes. Count that straight off qwen_managed_session_journal_tx (which carries writer_generation), keep the binding/transcript/terminal assertions, and align design D7 in both languages. The error string now also reports the old-writer tx count. * fix(managed-agent): qualify the journal tx schema in the freeze-arm gate The identity-based old-writer count queried qwen_managed_session_journal_tx unqualified; the runner's MySQL connection has no default schema, so the arm died with ERROR 1046 instead of evaluating the fence. Qualify it as qwen_managed_agent.qwen_managed_session_journal_tx like every other query in this runner. No behavior change to the assertions themselves. * fix(managed-agent): harden takeover correctness from the R1 review - settle turn_settled checkpoints through the load route's own tail instead of a terminal decline, and keep transient store verdicts (missing_state/missing_checkpoint) retriable on both authorization reads - exempt only takeover-shaped 409s from the pre-admission retry budget; every other failure kind meets it - scope the 120s load-timeout to recovery loads so plain attaches under the CHM bin locks stay on 30s - adoptGeneration: move map churn out of the connector monitor (lock-order inversion), evict only stale-boot entries on equal-boot mismatches - answer a duplicate submit of an already-settled prompt with a 202 replay anchored at its input.accepted sequence - withdrawSubmissionAttempted: guard on the same status window as its peer CAS methods; complete capability mismatch terminally in both remaining coordinator layers - runner: freeze-arm pre-freeze liveness assertion, /health-proven wake non-vacuity, teardown SIGCONT, exact-string dispatch-generation bounds - CI: job ceiling 95 with corrected ceiling math; pin all seven failover arms in hosted-process-ci.test.js - sync both design-doc languages with the shipped behavior * fix(managed-agent): close the R2 review round on the replaceable-host-harness slice - withdraw + cancel interference (R2-1, Critical): a CANCELLING Turn whose never-admitted mark was withdrawn is fast-cancelled instead of being re-dispatched into a second live execution at the adopted generation - the generation-mismatch catch escapes the pre-admission budget only on a bound Session, like every sibling arm (R2-3) - bound the remote-supplied decline reason to the error_message column so the terminal write itself never throws (R2-5) - the last fail(...) in the catch ladder is failTerminally with the exception logged, like its siblings (R2-6); the withdrawal itself is logged too (R2-7) - freeze arm: the replacement Spring inherits the original port so the woken writer meets a live fencing control plane, not a dead socket (R2-2); the wake block asserts the head actually advanced past the frozen owner's writer generation, drops the wrong heartbeat claim (R1-8 follow-up), separates the two disk-audit meanings, and asserts the retained Harness home (R2-8) - the hosted /capabilities feature list derives from the registry instead of a hand-maintained literal (R2-9) - docs: D7 scoped to the journal half of the Q2 exit check, the binding half and the pre-contract-404 classification recorded in Boundaries (R2-4, R2-10) * docs(sdk-java): correct the load-timeout coverage comment in the client test * fix(managed-agent): reference the frozen Harness through the children registry in teardown The R1-4 teardown SIGCONT referenced the try-block-scoped `harness` binding from the finally block, so every freeze-arm run died with ReferenceError after all fencing assertions had already passed (CI run 37009843147). Reach the child through the children registry by its start() name instead. * docs(managed-agent): scope the adoption sentence to this slice's takeover reach * fix(managed-agent): close the R3 review round at its five shared roots - retract the turn_settled settle tail to a typed decline: the tail answered 200 with no recovery watermark (Java then failed the bind, orphaned a completed journal turn), fabricated state:'completed' on phase alone, ran mutating work under a passive load, and retained no snapshot for the lost-reply retry. D10 already owns rebind-and-keep-reading as the Step 3 row (R1-25-re, R3-2 .. R3-5) - the load route's restore guard applies the same durable-vs-transient verdict read, because the bundle collapses both into one blocked bit and the durable decline was unreachable from production (R1-26-re) - the pre-admission exemption keys on lease-shaped 409 codes only: configuration-shaped 409s (e.g. hosted_tool_profile_conflict) meet the budget, and the same 409 exempts nothing on an unbound Session (R1-1-re, R1-30-re, R3-13) - equal-boot adoption keeps pendingRecovery: the markers were minted by the same live client (R1-29-re) - lifecycle capability mismatch: revert completing-as-unconfirmed — that skipped the drain, flipped the session, and recorded a clean row with no FAILED vocabulary to tell it apart; the reason-loud retry is the honest end of the line until an operator realigns versions, and the README sentence says so. Action-response outbox keeps its honest FAILED-with-code terminal (R1-45-re, R3-5, R3-15) - test hardening: concurrency close-once now surfaces worker assertion failures via Future.get and pins that no real client build happened (R3-12); loadTimeout-vs-requestTimeout discriminated at the call site (R3-9); single-walk acceptedInputSequence (R3-18) - runner: 5-column execution queries pin runtime_session_id unchanged under --harness-only (R3-14); frozen-arm teardown looks the child up by a shared label constant and fails the run on a drift (R3-17) - CI: ceiling 105 covers the nine summed step ceilings (92) plus the uncapped setup steps (R3-6); the seven-arm pin also asserts each script's flags and the install step's existence (R3-16) - docs: both languages re-synced (D5 retraction, D9a code allow-list, D7 teardown, D8 runtime identity, honest validation digests, the four missing changes-table rows, README retry clause) * fix(managed-agent): replay takeover snapshots only to a same-shape request A passive load only asks broker.status(), so its snapshot keeps phase='await_runtime'; replaying it to a drive load handed the coordinator a checkpoint that isContinuationReady() can never accept, terminally failing an otherwise continuable Turn. Record the request shape with the snapshot and replay only on a mode match; the clear conditions stay promptId-only. Also make the adoption race test deterministic: a package-private createClient() seam lets the test inject the replacement instead of a real /capabilities call (proven flaky on CI), worker assertion failures now surface through Future.get, and the pendingRecovery fixture seeds its marker through the recovery path. * fix(managed-agent): close the self-audit round on the replaceable-harness slice Three rounds of undirected plus adversarial audit over the whole PR diff (31 files), with three parallel deep-dive passes over the Java, TypeScript and runner/docs surfaces. Every finding was re-verified against the tree before acting; five reported defects were rejected on evidence. Correctness: - the load route read the blocked-restore verdict AFTER managed.close(); sealing the journal makes every later store read fail as "writer is not active", which the authority erases into missing_state, so the durable branch this PR added could never fire and a corrupt checkpoint stayed in the retry-forever class. Read the verdict first, and pin the order with a witness that goes red when the read moves back (the local authority's seal does not disable reads, so only the ordering assertion catches it). - drop hosted_session_already_attached from the lease-bounded 409 set: the daemon removes an attachment only on an explicit detach or delete and this control plane never detaches, so a Spring restart against a surviving Harness retried a permanent refusal forever instead of ending the Turn. - adoptGeneration evicts by boot identity in both branches instead of clearing, so a marker minted by a concurrent rebuild can no longer be erased between two adjacent clears (which left a live attachment with a null recovery report), and markers follow their attachment. - the post-withdrawal fast-cancel reads the row again: a cancel landing after the claim is invisible in the claimed snapshot, and acting on it submitted a live execution only to cancel it below. - a durable verdict re-read after settling now declines as checkpoint_blocked, matching the identical pre-settle classification. - an unrecognized blocked reason throws instead of declining, so a reason core adds later cannot silently become a terminal Turn failure. - the operator-settable load-timeout is validated at connector construction; a bad value previously threw on every client() call and surfaced as an endless transient retry naming only the exception class. Tests: already_attached and lost-withdrawal-CAS witnesses; the fast-cancel witness now pins the re-read (cancel after claim); the concurrent-adoption test surfaces worker assertions through Future.get and injects the replacement through a package-private createClient() seam instead of standing up a live /capabilities call; the CI pin gains each arm's mode flag and a ceiling-sum invariant. Docs (both languages): D1 now describes the boot-identity eviction and lists what this slice really added to HostedHarnessClient; D3 no longer claims the bind-refusal path stops being reached (it is D4's trigger); D6 records the same-shape replay clause; D7's fence rationale no longer blames heartbeats (lease renewal touches writer_lease_until only); D9a names the two exempt codes and why already_attached is not one; the validation digest states which claims are pinned and which are not; the CI arithmetic reads 12 + 8x10 = 92; the stale workflow nit is dropped. * chore(ide): regenerate NOTICES for the merged dependency set (email channel) * fix(ide): replace the workspace-polluted NOTICES with the pristine regeneration The file committed in |
||
|
|
29fd4c1ab6
|
feat(managed-agent): broker authentication and broker-provisioned writer credentials (#13210)
* feat(managed-agent): broker authentication and broker-provisioned writer credentials
Close the five gateway-free security gaps from #13180:
- signed auth mode: the broker authenticates the tenant/actor header pair
itself via HMAC (X-Qwen-Signature + timestamp), with startup guards that
refuse non-loopback binds, weak keys, a contradictory trusted-actor
stand-in, and a context path that would bypass the path filters
- writer credentials are broker-provisioned HMAC bindings over
(tenant, workspace, session) and enforced at every session-store entry
point; the harness receives them through the attach payload
- /internal/** can bind to its own loopback connector with 404 routing
between the two surfaces
- sessions record their creator (V28), so hosted approvals are answerable
over HTTP and workspace approvals bind to the authenticated actor
- the TS client refuses plaintext http:// broker URLs on non-loopback
hosts unless the broker opts in via the attach payload
Includes the bilingual design doc (docs/design/managed-agent-broker-auth)
and verification coverage: signed-mode E2E, dual-port routing E2E, the
binding sweep over every store entry point, and the hosted ownership
chain.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(managed-agent): harden broker guards from adversarial audit
- refuse internal-server.port equal to server.port at startup: the surface
routing filter classifies by local port, and equal port numbers on
different addresses would serve /internal/** on the public address
- refuse reusing the signing key as the writer binding key: the two HMAC
keys protect different domains
- log the resolved auth mode, internal listener and writer binding state
at startup, as the design doc promises
- log the refused managed session store descriptor in the hosted harness
so the transport guard's remedy stays discoverable
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(managed-agent): renumber the session creator migration to V31
main gained its own V28 and V30 migrations while this branch was in
review; move the creator column to V31 to keep the Flyway sequence
unambiguous.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* chore(web-shell): regenerate the managed agent API types
The v1.28 Unauthorized response description changed; the generated
client must match the contract.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(managed-agent): address the PR review round on broker auth
- sign the request body, query string and Idempotency-Key too, so a
captured signature authorizes exactly one request (review R1-3)
- resolve the public port to the Spring Boot default 8080 when
server.port is unset, so the equal-port guard cannot slip (R1-1)
- read the session creator row null-tolerantly instead of 500ing on a
missing row (triage #3), and make the writer credential policy a
required injection so it fails closed (triage #5)
- startup guards: allowed-drift >= 1s, loopback store base-url must name
the internal listener's port, and an enabled harness over plaintext
non-loopback http is refused (the attach payload carries credentials)
- the routing filter answers the standard error envelope, the internal
connector checks its address property was accepted, and a TLS public
listener beside a plaintext internal one is warned about
- docs: OpenAPI scheme descriptions, README recipes and both design docs
now match the implementation; the ownerless fallback is documented as
tenant-scoped for anonymous and pre-migration Sessions
- tests pin the drift window, unparsable timestamps, body/query/key
coverage, the workspace term of the writer MAC, WebShell creator
recording, the publication-writer credential check, and the producer's
omit-when-unset payload
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(managed-agent): renumber the session creator migration to V33
main took V31 (workspace recovery bundle) and V32 (workspace session
close) while the review round was in flight; move the creator column to
V33.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(managed-agent): renumber the session creator migration to V34
main took V33 (agent definitions) during the latest rebase. Also make
WorkspaceRecoveryStoreTest compare the session table byte[]-tolerantly:
creator_actor_key is the first VARBINARY column in that table, and byte[]
compares by reference inside Map.equals, which failed the immutability
assertion on MySQL.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(managed-agent): create the pre-upgrade session with the legacy column set
The retention IT pins the schema at V31 and then creates a session, but
the store insert now always names creator_actor_key (V34), which a
database at the prerequisite version does not have yet. Write the
pre-upgrade session with the V31-era column set directly, then let the
close and retirement flow run through the store as before.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(managed-agent): close the signed-mode coverage, buffering and contract gaps
- Cover the bare POST /v1/agents route and normalized path spellings by
deciding filter coverage on the routed path (shared PublicSurface
predicate), while the canonical string keeps signing the raw URI.
- Bound the buffered signed body (auth.max-signed-body-bytes, default
10 MiB) and answer 413 before the signature comparison; refuse a
repeated Idempotency-Key header with 400.
- Classify the internal surface on the routed path as well, so encoded or
parameter-carrying spellings cannot cross the listener boundary; the
routing filter now shares the injected ObjectMapper.
- Refuse a plaintext session-store base URL whose host falls outside the
client's literal-only loopback set, guard spring.mvc.servlet.path
alongside context-path, and enumerate the guards allow-insecure-bind
skips in the startup posture line.
- Qualify the creator-only responder wording in the OpenAPI contract with
the ownerless fall-through, declare the 401 refusals per route, and pin
both with contract tests.
- Restore the transaction boundary on the legacy 9-arg insertSessionCommand
default, share one test signer between the integration tests, and carry
writer_credential_invalid in the shared error fixture.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(managed-agent): renumber the session creator migration to V35
main landed V34 as managed_tool_output_collection; move the creator
column to V35 and update the design docs, the OpenAPI wording and the
test references.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* chore: restore NOTICES.txt to the upstream content
The first merge commit swept in a locally regenerated NOTICES.txt whose
content depends on the host's installed tree; CI regenerates it on Linux
and expects the upstream content.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(managed-agent): address the R3 review round
- Narrow the replay and body-bound claims to what the code guarantees
(captured signatures stay fixed to method/path/query/tenant/actor/body;
the body bound is per request, with the worker pool as the aggregate
ceiling).
- Presize the signed-body buffer, pin the raw-URI signing split and the
chunked bound, and add a route-table coverage test for the shared
public-surface predicate.
- Guard a plaintext non-loopback tool-publication service URL, normalize a
blank internal address, warn for a non-loopback internal listener
without TLS, and stop classifying a bare 127 as loopback.
- Declare 413 per operation with a shared PayloadTooLarge component, state
the ownerless approval fall-through's create-command step, and parse the
shared fixture structurally in the qwencode test.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(managed-agent): address the R4 review round
- Classify surfaces on the router's own parsed path segments, so a
Content-Type charset cannot make the classifier and the router disagree
(R4-4).
- Cap the signed-body presize hint at the chunk size; derive the URL host
from the authority when URI.getHost() is null; validate the publication
service URL against its consumer's rule; warn on a non-loopback internal
listener regardless of public TLS; align the ownerless 403 wording with
the requireOwner conjunction.
- The contract guard enumerates the responder operations structurally;
read-side operations drop their 403 sibling descriptions and the two
responders name actor_scope_mismatch.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(managed-agent): reconcile the README intro with the signed mode
The page-1 paragraph still described the pre-authentication arrangement
(no auth layer, header-asserted tenant, bind anywhere with
QWEN_MANAGED_AGENT_SERVER_ADDRESS). A non-loopback address now refuses to
start in the default auto mode, so say what leaving loopback requires
(R3-16).
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(managed-runtime): mark the fake-host store test as allowing insecure http
The sealed-renewal test (from main's
|
||
|
|
17c182eda0
|
fix(managed-agent): stop virtual-thread carrier pinning in the Hosted Harness stream and the Runtime Broker guards (#13388)
The managed-agent server reads every Turn's SSE stream on a virtual thread (newVirtualThreadPerTaskExecutor). HarnessEventStream.next() held a monitor across the blocking socket read, so once open streams reached the ForkJoinPool carrier count every carrier pinned and no virtual thread in the process ran again — Turns wedged after admission with zero journal commits, deterministic at >= #cores concurrent Turns on JDK 21 (JEP 491 exempts JDK 24+). The Runtime Broker repeated the pattern: BindingRenewal/DispatchRenewal monitors and every synchronized (context) block in RuntimeBrokerService guarded blocking repository calls (session/binding/execution findById, compareAndSet, renewOperation, hasActiveByRuntimeSession) from the same virtual threads, pinning carriers on contended database rows after the stream readers were unpinned. Convert all of them to ReentrantLock with the same guarded regions — a VT parked on the lock unmounts instead of pinning. Semantics preserved: same mutual exclusion, reentrancy (next() -> close(), persistResourceHandle -> closeLocked, SessionContext accessors under caller-held guards), uninterruptible acquisition, and the stopped-flag tick-retraction ordering witness (DurableRuntimeRecoveryTest.stoppedFlagRetractsATickQueuedBehindClose) stays green. Witnesses added, both deterministic on JDK 21: - HarnessEventStreamPinningTest holds carriers+2 SSE bodies open and proves an unrelated virtual-thread probe still completes; restoring the synchronized next() starves it (32 s, progress=1). - BrokerVirtualThreadPinningTest parks carriers+2 acquire calls inside SessionContext guards at a latched repository call; restoring synchronized (context) pins the carriers (only carrier-count callers ever arrive, awaiting 60 s). Verification: qwencode 173 tests, runtime-broker 601 (incl. the two witnesses), managed-agent-server 567 + HostedConcurrentTurnBurstMySqlIT 6/6 rounds on MySQL 8.0.46, checkstyle clean; full packaged-stack measurement (16 concurrent turns settling after previously wedging) reproduced pre-fix during #13365 review and will be re-run on the rig. Refs #13333 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
561b606b19
|
fix(managed-agent): surface the named load refusal from mixed-version takeovers (#13349)
A Harness that cannot open a Session journal (e.g. it predates the message.delta event kind, after a rollback or during a rolling deploy) refuses POST /session/:id/load fail-closed with 503 and a machine-readable code on the wire — that much held since 0.24.7. The Java client then collapsed every 5xx into MutationOutcomeUnknownException without reading the body, so the coordinator retried forever with only failure=MutationOutcomeUnknownException in the log and, once the pre-admission budget ran out, recorded hosted_harness_unavailable — a shape indistinguishable from a genuinely down Harness (#13320). loadSession now reclassifies an ambiguous-status failure whose body names a refusal code as HarnessSessionRefusedException. The code must match the snake_case vocabulary the Harness writes (bounded by the turn table's error_code column); transport failures and code-less or off-vocabulary bodies keep the outcome-unknown classification, so a proxy 503 or a crash before the refusal writer is still retried as ambiguous. The coordinator keeps retrying a named refusal — the Turn still awaits a Harness build that can read the journal, and a mixed fleet rolls forward — but the retry logs now name the refusal code, and the terminal failure records it instead of hosted_harness_unavailable, so the rolling-deploy runbook can tell a mixed-version refusal apart from an outage. The failover design doc records the released 0.24.7 refusal shape and the new coordinator surface in both languages. |
||
|
|
728c13de21
|
feat(managed-agent): Hosted Turn takeover and G1 failover E2E (#13083)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Lint & Static (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / web-shell E2E Smoke (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (no-AK, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (ubuntu-22.04) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (windows-2022) (push) Blocked by required conditions
E2E Tests / Build for E2E (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 1/2 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 2/2 (push) Blocked by required conditions
E2E Tests / E2E Interactive - OpenTUI renderer (bun) (push) Blocked by required conditions
E2E Tests / channel-plugin E2E (nightly) (push) Blocked by required conditions
E2E Tests / cron-interactive E2E (nightly) (push) Blocked by required conditions
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Runtime Broker and Managed Agent MariaDB / Java 21 (push) Waiting to run
SDK Java / Hosted process fault gates / MySQL 8.4 / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* feat(managed-agent): Hosted Turn takeover and G1 failover E2E Implement the Harness half of Stage G Turn takeover (#12952 G1): a replacement Hosted Harness loads a Session parked at an await_runtime / results_ready checkpoint, settles its Runtime executions under their original executionCallId (never replaying the model Prompt), reports the recovery snapshot the Java coordinator already parses, and continues or cancels the turn through the new managed-runtime routes. Assistant text streams as activation-scoped message.delta journal events so a published prefix survives its writer. Ungate --inflight-failover / --continuation-failover against the packaged stack: public Workspace admission carries the fixed files profile, the runner seeds the registry/grant and opts into durable local Workers, and both modes run in the Hosted MySQL CI job. The modes require Linux (the W0e durable reclaim) and refuse other platforms with an explicit error. A default-off trusted-actor-header property stands in for the gateway principal on single-host/E2E deployments. * fix(managed-agent): let the takeover owner adopt a settled Turn's checkpoint A replacement Harness feeding the settled batch to the model ran under its own activation, so the next tool batch was refused as Runtime work of the prior activation and the Turn stayed RUNNING with no terminal event. When consumeRuntimeResults runs under a different activation with every item settled, commit the consumed checkpoint under the current activation. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: probe <probe@local> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
78143fe335
|
feat(managed-agent): Serve durable permission Actions (D6b) (#13101)
* feat(managed-agent): Serve durable permission Actions (D6b) * test(managed-agent): Avoid concurrent Action mock stubbing * fix(managed-agent): renumber the Actions migration to V24 and bump the contract to 1.25 Merging main brought in #12946's V23__managed_mcp_records.sql next to this PR's V23__managed_actions.sql; Flyway refuses two migrations with the same version, so the server would not start. The Actions migration becomes V24 with its content unchanged. The contract takes 1.25.0 on top of main's 1.24.0 (#12946), with a v1.25 note for the Actions routes this PR serves. * fix(managed-agent): address the D6b review follow-ups A Workspace Session whose connector was built without the Managed Action store silently fell back to the deployment approval mode and skipped the Harness mode confirmation -- the case the design declares fail-closed. createOrLoad now refuses it, so the two remaining null checks in the connector are gone and an un-wired store fails loudly at admission. requireOwner let an IllegalArgumentException from actorKey escape as an unmapped 500. It now answers 403 actor_scope_mismatch, matching the neighbouring workspace routes. endedCode maps cancelled to action_cancelled but no test asserted it: a cancelled Action now fails its response operation with that code and returns no resolution. The design docs still named the Actions migration V23 after it moved to V24; V23 belongs to the Session MCP catalog (#12946). --------- Co-authored-by: 易良 <1204183885@qq.com> Co-authored-by: yiliang114 <jinjing.zzj@gmail.com> |
||
|
|
a63157304a
|
feat(managed-agent): enable G0 public Workspace file turns (#12955)
* feat(managed-agent): enable G0 public Workspace file turns * fix(managed-agent): preserve Workspace admission refusals * fix: repair skill listing fixture and Workspace capability docs * test(runtime-broker): pin durable worker crash window |
||
|
|
1d03a07f35
|
test(sdk-java): close the connection on Hosted Harness no-content responses (#12824)
The Java 11 lane of SDK Java fails intermittently in HostedHarnessClientTest.coversLoadStatusTranscriptHeartbeatAndLifecycleMutations: a teardown call raises MutationOutcomeUnknownException wrapping "HTTP/1.1 header parser received no bytes", while Java 17, Java 21, macOS and Windows stay green in the same run (seen on #12816). It is the fixture problem #10472 fixed for DaemonSessionClientTest. On Java 11 the JDK's own HTTP server drops the connection after a response without a body, while the Java 11 HttpClient keeps that connection pooled, so the next request over it reads EOF before any response byte. The test answers cancel, detach and session delete with 204 back to back. HostedHarnessClientTest arrived later (#12654) with its own 204 helper, which left the connection persistent. Mark that helper's responses non-persistent, as #10472 does. On Temurin 11.0.29 the test failed 115 of 200 rounds pinned to one core and 25 of 200 unpinned before this change, and 0 of 200 in both after. All 18 methods of the class pass 50 of 50 rounds on one core after it. JDK 25 passed 200 of 200 rounds before it. |
||
|
|
585466c646
|
feat(sdk-java): Add the Hosted Harness private client (#12654)
Add the Java control-plane transport for the hosted Harness private protocol from #12409. Construction performs capability negotiation, validates the advertised capability digest against the configured deployment digest, and pins the process boot identifier; every later request then carries the protocol and boot-ID fencing automatically, and a generation mismatch surfaces as a non-retryable conflict rather than a retry against a restarted Harness. Sessions are created, loaded, heartbeated, prompted, streamed, and cancelled through typed request objects; turn submissions carry a precomputed payload digest, and event streams reuse the daemon SSE reader with replay cursors. The client has no production caller yet: it is the transport the Spring control plane will wire in a later slice, in the same pattern as the attestation client before process adoption. Related to #12380. |
||
|
|
f5beafbdd6
|
chore(sdk-java): bump fastjson2 to 2.0.65 (#12465)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
8b72f4d456
|
fix(java): make SDK worker threads daemon (#9925)
* fix(java): make SDK worker threads daemon Use daemon threads for the Java SDK default executors so background helper pools do not keep example or short-lived JVM processes alive after work completes. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * fix(java): document and test daemon pool lifecycle Keep examples and docs aligned with daemon executors, and ensure the client module actually discovers its JUnit 5 regression test. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * fix(java): reuse the custom example executor Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * test(java): isolate SDK integration cases Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com --------- Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com> |
||
|
|
834b66d209
|
fix(java): read control response subtype from payload (#9924)
* fix(java): read control response subtype from payload Use the parsed control response wrapper when deciding whether a CLI control response is an error, matching the nested wire format and preserving the legacy top-level fallback. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * fix(java): keep sendPrompt stream aligned after control errors Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(java): align control error fixture Use the real control-response error envelope in session tests so the subtype handling coverage matches the CLI wire format. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * fix(java): simplify control response subtype lookup Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * test(java): cover control response warning logs Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * test(java): strengthen control response subtype coverage Cover missing response payloads, non-error subtype boundaries, and warning payload content so subtype detection regressions are observable. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * test(java): pin control response subtype boundaries Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
3aa1b14624
|
ci: stabilize tests under shared ECS host contention (#10552)
* ci: tolerate shared runner scheduling delays Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(ci): inherit ECS contention budgets Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): extend PTY request budget on ECS Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): synchronize ACP archive race Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(ci): isolate visuals publisher fixture modules Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(ci): cover hostile parent package scopes Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(sdk-java): isolate finite SSE fixture connections Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): make timer-range deadline deterministic Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(sdk-java): tolerate shared CI scheduling delays Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(sdk-java): preserve timeout contract on CI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(sdk-java): include request submission in timeout bound Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * ci: extend no-AK shared runner timeout Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(ci): guard no-AK integration budget Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * ci(sdk-java): serialize ECS test execution Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(sdk-java): relax stalled-response setup on CI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(ci): align no-AK timeout contract Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * ci: extend Serve A/B shared runner budget Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * ci: scope extended ceilings to ECS routing The extended job ceilings (test 60->90, integration_no_ak 30->60) were unconditional even though runs-on falls back to GitHub-hosted ubuntu-latest for fork PRs from untrusted authors and when MAINTAINER_ECS_RUNNER_DISABLED=true. Derive each ceiling from the runner classify_pr selected so hosted paths keep the pre-contention bound and a genuine hang there does not burn the extra 30 minutes. The lane contract now evaluates the real timeout expression for both routings instead of pinning a bare number, matching the substitute-then-evaluate technique ci-runner-routing.test.mjs uses on runs-on. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): address closeout review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
8104071260
|
docs(java): correct closed session exception (#9859)
* docs(java): correct closed session exception * docs(java): clarify session control exceptions Clarify which Java SDK entry points throw SessionControlException directly and pin closed-session behavior with a lightweight unit test. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * test(java): pin unavailable session exceptions Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * test(java): pin session exception wrapping Add coverage for direct session initialization failures and both QwenCodeCli.newSession wrapping paths so the Java SDK docs stay aligned with the exception contract. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com --------- Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com> |
||
|
|
e5838f4145
|
test(sdk-java): close the connection on body-less fixture responses (#10472)
The Java 11 lane of SDK Java fails intermittently in the daemon client tests: a session teardown raises DetachOutcomeUnknownException or MutationOutcomeUnknownException wrapping "HTTP/1.1 header parser received no bytes". It has hit five different test methods over the past day on unrelated branches, while Java 17, Java 21, macOS and Windows stay green in the same runs. The fixture causes it. On Java 11 the JDK's own HTTP server drops the connection after a response with no body at all -- a 204 detach, a 204 cancel, a 204 session delete, an empty event stream -- while the Java 11 HttpClient keeps that connection pooled. The next mutation the SDK sends over it reads EOF before any response byte, and since the request is not idempotent the SDK correctly declines to retry and reports the outcome as unknown. Pairing the halves across JDKs shows both must be Java 11: a Java 11 server with a Java 21 client never fails, nor does a Java 21 server with a Java 11 client, so only the fixture is affected. Route every body-less response through a helper that marks the response non-persistent, so the client never pools a connection the fixture is about to drop. This generalises #10365, which applied the same treatment to two handlers of a single test. On Java 11, pinned to one core: the three test methods seen failing in CI failed 50 of 200 rounds before this change and 0 of 200 after. |
||
|
|
c1444bdc40
|
fix(java): avoid Java 11 HTTP fixture reuse (#10365) | ||
|
|
435199716b
|
fix(artifacts): refresh updatedAt when artifact content changes (#9929)
* fix(artifacts): refresh updatedAt when artifact content changes Content edits and same-size HTML republishes left updatedAt equal to createdAt, so previews stayed stale. Treat fingerprint and observed status/size changes as material updates. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(artifacts): record the size baseline when content hashing is skipped The workspace size baseline was only written alongside a sha256, so files too large to hash — and snapshots restored without one — had no baseline left once the public sizeBytes started tracking the live stat. A single content change then bumped updatedAt twice and let status fall back from changed to available on the next refresh. Also restrict the published content hash to the trusted artifact tool path and drop the unreachable published branch from the preview freshness key. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(artifacts): include clientRetained on restore fixture acp-bridge compiles tests in tsc --build, so a restore snapshot missing the required PersistedSessionArtifact field failed CI prepare. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(artifacts): do not keep deleted write_file intermediates write_file auto-recorded HTML used only to print a PDF, then the model deleted the file and left a missing artifact card. Intermediate writes can now opt out, scratch paths under .qwen/tmp are not registered, and a vanished write_file workspace file is dropped on the next list/get. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(sdk-java): wait for SSE teardown before detach after a 204 Java 11's HttpClient can reset the follow-up POST /detach when the unexpected-success SSE stream is still closing. That turned unexpectedSuccessfulSseStatusIsProtocolFailure into an Error after the assertion already passed. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(artifacts): forget write_file artifacts only after confirmed absence Stat errors such as EACCES were reported as missing and then permanently removed. Keep those entries until ENOENT is confirmed, persist vanished removals strictly, and only bypass the refresh TTL while a write_file artifact is still available. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(artifacts): restamp write_file fingerprints and recheck before forget Available write_file artifacts were rehashed on every poll after an mtime-only drift, and get()/list() could tombstone a file that came back or disagree after a failed persist. Restamp the in-memory baseline when content is unchanged, re-stat confirmed-missing write_file entries before forgetting them, and return the restored artifact from get() when the tombstone does not land. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(artifacts): refresh write_file presence before overflow eviction evictOverflow still used the TTL-only refresh filter, so a write_file file deleted inside 5s could keep a stale available status and push a healthy artifact out. Use the same presence recheck as list/get, and wait for SSE teardown on the remaining Java 11 error exits. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(artifacts): bump updatedAt on later same-size content edits A second edit while status is already changed left the preview freshness key unchanged. Track the last observed fingerprint, and persist vanish-forget as eviction so a later client record of the same path is not tombstone-suppressed. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(artifacts): drop stale observation state on re-register A changed-to-available re-record kept lastObserved fingerprints and bumped updatedAt on the next refresh. Clear them in merge, keep stat-error missing artifacts out of eviction, and pin the size baseline before adopting a live size. Co-authored-by: Cursor <cursoragent@cursor.com> * test(artifacts): pin remove updatedAt against a fake clock The createdAt/updatedAt inequality raced on machines where upsert and remove landed in the same millisecond. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(artifacts): scope get() vanish-forget to the requested artifact A sibling left in clean-missing after a persist rollback can have its file come back; get() must not tombstone it from that stale observation. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |