mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-09-12 03:57:07 +00:00
1728 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
54a64e4b75 | fix(web-shell): retain localized workspace error guidance | ||
|
|
e745c20ac5 | fix(web-shell): preserve discovery errors and stabilize history smoke | ||
|
|
c3325d274f
|
Merge branch 'main' into codex/fix-web-shell-cold-session-loading | ||
|
|
3a262e0914
|
feat(ipc): drop peer messages that arrive faster than a session can take, and tell the sender once (#11277)
* feat(ipc): drop peer messages that arrive faster than a session can take, and tell the sender once The inbound gate decided whether a peer message may act. Nothing decided how fast messages may arrive, and everything downstream costs something per message: the hold buffer evicts its oldest entry per arrival, so a flood walks a user's real backlog out one message at a time; the input queue fills; and every outcome draws a receipt over an outbound ceiling shared with everything else the session sends, so the receipts most likely to be lost are the ones belonging to the legitimate messages arriving alongside the flood. Meter arrivals before policy runs. Thirty from one sender at once, then one every two seconds; sixty across all senders, then one a second. The second limit exists because a sender names itself on the frame, so rotating that name buys a fresh allowance from the first limit but not from the second. Neither is a security boundary — a hostile same-uid process has better options than flooding a socket — they bound the damage an ordinary bug does, such as two same-class sessions replying to each other automatically. Judge repeats on the body rather than the id: a model in a retry loop mints a fresh id every time, so the id guard cannot see it. Only for messages from another session. A process this session started and a controller the user minted a token for are exempt, because a hook that reports two builds is reporting two facts and a person who says "continue" twice means it twice; both are still rate limited. A dropped message is never held, never delivered, and leaves no tombstone, so a sender that waits out its burst and retries still lands. The sender learns through a new `dropped` receipt carrying `dropReason` and, when one receipt stands for several messages, `droppedMsgIds` — the first drop is answered immediately and the rest are folded into one receipt every few seconds, because a report about a flood must not scale with it. The receiving user is told at most once a minute per sender, with a count of what that line stands for. A full input queue is now a drop naming the queue instead of an expiry, which claimed a decision had run out when none was ever pending. Sending sessions mirror the receiver's bucket per address and refuse a send the receiver would drop, so the model is told to batch before the message is written rather than a round trip later, and the receiver never spends a connection on a message it was going to turn away. The mirror is never stricter than the real limit, and a `rate-limited` receipt empties it: the receiver is the authority on its own level. No hop chain here — cutting a relay loop properly needs the path carried on the frame and the current turn's inbound message known at send time, which is separate plumbing. The buckets bound a loop to one message every two seconds meanwhile, and adding the field later breaks no reader. Part of #10925 * fix(ipc): keep peer drop reports truthful at the meter, mirror, and close (#11277) - judge the repeat window on the larger of the wall and monotonic deltas, so a system suspend cannot turn an honest re-send into a duplicate - roll the admission's body record back when an admitted message could not be delivered, so a retry after queue-full is not dropped as a repeat of a message that never arrived - mirror the receiver's duplicate verdict in the send-side pacer instead of charging a token per send, and keep the burst count a refusal quotes this session's own - defer an over-budget drop receipt to the next window rather than discarding it, so a legitimate sender under someone else's flood still learns its message died - stop arrivals before draining the receipt coalescer at close and let the flush wait inside the cleanup budget, so receipts owed at shutdown reach their senders - skip the socket-binding drop tests on Windows * fix(ipc): keep the drop path honest about what it turned away, and about who Round two of review on the admission work. Six behaviour defects, each one a case where the reporting or the metering said something that was not true. **A full hold buffer no longer evicts.** It used to make room by discarding its oldest parked message and telling that message's sender it had `expired` — so an arrival destroyed a message the user had not read yet, and an uninvolved third party was told a decision had run out that nobody was ever making. A flood walked the backlog out one entry at a time, which is the first thing this feature exists to stop. The newcomer is turned away with `queue-full` instead, the shape the accept path already had: the cost falls on the sender that could not fit, it is told the truth, and it can retry. With no eviction left, `expired` means only what the design doc says it means. **The repeat record is rolled back wherever a message is settled unseen.** A body is recorded when it is admitted, but admission is not arrival: the gate can still refuse it, fail to queue it, park it into a full buffer, or be shutting down. Left behind, the record turned the sender's honest retry into a `duplicate` — a verdict whose whole premise is that the content is already at the far side. Worse on the refusing path, where the sender's "stop, nobody will see this" became "fold it into a later message". The rollback restores what the admission displaced rather than clearing the slot, so the body admitted before the failed one keeps its own protection, and the token stays spent. **The metering key no longer takes a peer at its word.** It is chosen by what the transport established and the self-asserted address is namespaced inside it. A peer could otherwise write `from: "own-process"` and share a bucket, and a transcript line, with the scripts this session itself started: it would spend their allowance, its flood would be announced to the user as coming from their own process, and the receipts would be addressed to a socket path that does not exist. The address is also bounded now, since it is retained as a map key in three tables and nothing that can be dialled comes near the cap. **An evicted receipt batch no longer keeps a live timer.** It left the table that `flush` and `dispose` iterate while still re-arming every few seconds, so under a rotating `from` the orphans accumulated without bound, each pinning a rejected message. **A waiting batch holds an address, not a frame.** It kept the whole untrusted `PeerUserFrame` for the trail, and longer while the receipt budget was spent, so a flood the meter turned away lived on in the heap of the session that turned it away. It keeps the three fields a receipt actually addresses. **A deferred receipt is bounded, and the close path forces one out.** Deferring an over-budget receipt made its age unbounded, and a sender turns a `rate-limited` receipt into a live throttle on itself — so a minutes-late one paced an innocent sender against a wall that was gone. Past that age it is abandoned. At the other end, `flush` used to dispatch nothing at all when the budget was spent, which is exactly the sender the deferral was added for. Also: `close()` no longer serializes an unbounded leg ahead of bounded ones, so the corrective receipts and the registry clear happen inside the exit budget rather than after it; a refund can no longer un-drain the mirror it raced, or erase a body record the receiver still holds; the mirror refuses a repeat rather than writing a frame it knows will be dropped; a drain corrects the level without re-anchoring the burst window or inventing a mirror for a target nothing was sent to; and a drop notice raised before the UI subscribes is replayed rather than swallowed after spending its sender's minute. The user-facing copy is corrected the same way: the trust category leads the receiving line, `rate-limited` no longer accuses the named peer of a rate it may not have set, the suppressed count is no longer called "similar" when it is a session-wide total, a `duplicate` is no longer told to re-send what was already accepted, the dedup window is read from the constant rather than restated, and the expiry sentence keeps its retry advice for the cases that are not a shutdown. * fix(ipc): keep peer admission mirrors consistent * fix(ipc): reconcile peer admission accounting * fix(ipc): align peer pacing with session state * fix(ipc): keep a late drop receipt useful, and bound the reply token where it is held Five follow-ups on top of the round-5 fixes, all in the same PR's own surface. A `rate-limited` receipt that waited out a spent receipt budget was abandoned on the receiving side once it aged past one bucket refill. That traded a stale throttle for a worse silence: the ids it named stayed `pending` on the sending side forever, and a sender told nothing cannot tell a drop from a delivery that was ignored. The receipt now always goes, and the part of it that can go stale — the throttle it implies — is judged where the answer is known: the ledger stamps when each frame was written, and a drop is decided the moment a frame arrives, so the age of the send is the age of the drop. Past one refill the ids still settle and the mirror is left alone. `parsePeerFrame` refused a whole frame whose `replyToken` was longer than the drop reporter retains. The message may be perfectly ordinary; the token only routes the receipt. The bound stays where the token is retained, and the frame is delivered. `DropReceiptCoalescer.note` accepted its arguments two ways for a caller that does not exist — the method is new in this change. The global burst's own reasoning was in neither document: it is half the 64-send outbound ceiling because every admitted message draws a receipt against that ceiling. Both docs say so now, the module header no longer claims the per-sender bucket is the binding one, and a test pins the relationship so a retune of either number cannot quietly break it. Three guards had no witness: the meter reset when a session id changes, and the repeat-record rollback at hold expiry and at a policy flip. Each new test fails when its guard is removed. * test(cli): give the settled-receipt stubs the age the ledger now reports `SettledPeerReceipt` carries `ageMs` so the drop path can tell a live rate limit from one the receiver has already refilled past. The injected stubs in the messaging tests still returned the old shape, which type-checks against a stale core build and fails against a fresh one — so the package build, which is what CI runs during install, was the first thing to see it. --------- 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: qqqys <266654365+qqqys@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
9163bdcddb
|
feat(serve): one-command remote start with a generated token, same-origin shell access, and a pairing QR (#11172)
* feat(serve): make non-loopback binds usable with one command Binding 0.0.0.0 used to require coordinating four things by hand: an operator-generated token, the exact reachable browser origin in --allow-origin, a hand-built #token= URL for the browser, and patience for a shell that 403ed every mutation without the allowlist entry. - A non-loopback bind with neither --token nor QWEN_SERVER_TOKEN now generates an ephemeral 256-bit bearer instead of refusing to boot, printed once at startup with the concrete local/private-LAN URLs and a secret-bearing QR for phone access. Explicit sources keep precedence; explicitly empty sources still fail closed; a localhost request resolving off-loopback never generates. The QR re-publishes a stable operator token only at an interactive TTY, never into captured stdout. Advertised addresses reuse Local Control's private-LAN population, so VPN/docker/public addresses are never printed or encoded. The block writes through the non-throwing stdout helper so a vanished pipe reader cannot kill the listening daemon. - The built-in Web Shell's same-origin HTTP requests pass the origin wall on a remote bind once they carry a verified bearer; forwarded headers never establish same-origin trust, cross-origin and null origins stay denied, and Local Control listeners are excluded. WebSocket routes (terminal, voice) and TLS-front-proxy origins deliberately keep the allowlist requirement, named by a narrowed startup warning and the docs. - The standalone browser bootstrap gains a credential gate: token form on 401, distinct origin-policy message on 403, auto re-probe honoring Retry-After during cold start, permanent startup failure surfaced instead of polled, 10s-bounded abortable probes, bilingual en/zh-CN copy, per-tab token storage, and URL scrubbing in every build mode. - The pre-auth shell discriminators move to a dependency-light module so the serve fast-path static closure keeps deferring the express-static/CSP machinery; the import-boundary guard now covers the transitive edge. Docs: user guide remote quickstart + authentication/options/security sections, developer daemon boot-refusal references, CLI help, and a design doc recording the security decisions and three deliberate deferrals. Tests: token resolution/precedence/rotation, LAN filtering and QR gating, same-origin middleware incl. production mount position, bootstrap-gate retry/timeout/auto-recovery, DEV scrubbing, env-guard and fast-path guards. * feat(serve): shorten the generated remote bearer to 128-bit base64url A 64-hex credential was painful to type into the Web Shell gate and made the pairing QR denser than it needs to be. The generated bearer is only ever attacked online (it is never stored, rotates per process, and the daemon answers 401 without side effects), so 128 bits of CSPRNG entropy in 22 URL-safe base64url characters keeps the brute-force budget astronomical while being typeable and scannable. Operator-supplied tokens and the loopback --open-with-auth credential are unchanged. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * feat(web-shell): present the daemon credential gate as a device-authorization card The pre-mount gate rendered as bare unstyled HTML. Compose it from the shared Card/Button/Input/Label primitives with semantic theme tokens: centered card, daemon address as mono description, code-style token field, full-width primary action, and a bilingual security hint — the same visual grammar as familiar device-authorization pages. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(web-shell): theme and scope the credential gate like the app root The gate rendered before App mounts, outside the scoped Tailwind/shadcn scope and without the theme palette, so it fell back to unstyled light markup. Opt into the same scope attributes, theme module class, and dark class the app root applies, and pass the resolved initial theme from the boot entry. * fix(serve): address PR review — sweep boot-contract docs, harden quickstart edges, refine gate retry semantics Review responses for #11172: - Docs: sweep every standing statement of the boot contract (user guide bullet and flag rows, developer auth/quickstart/runtime references) to the new contract — generation supplies the remote token, same-origin HTTP needs no --allow-origin while WS upgrades and TLS front proxies still do; scope the LAN-advertisement guarantee to wildcard binds and document the container/pty/name-heuristic caveats. - Daemon: reject an empty --hostname as operator error before credential generation; treat inet_aton wildcard abbreviations as wildcards; prefer a routable private address for the QR; make the QR fallback line state-independent; suppress the quickstart when the socket actually binds loopback; move access-log and trace capture ahead of the origin wall so their rejects are recorded; normalize Host (case, default ports) in the same-origin check. - Web Shell gate: key permanent startup failure on body.code, parse HTTP-date Retry-After, show not-ready copy for 429, clear a rejected stored credential instead of pre-filling it, tag the gate root with data-web-shell-gate so e2e readiness locators no longer resolve on it. - Tests: call-site spy for the quickstart arguments, wildcard/QR-candidate fixtures, Host normalization and listener-exclusion cases, gate retry shapes, storage-blocked cache pin, main-boot mock completeness, and the fast-path guard transitive edge. * docs(serve): scope the remaining allow-origin refusal statements to loopback binds The --allow-origin flag row and the auth reference's allowOriginCors bullets still stated the '*' / non-loopback-origin boot refusals unconditionally; on non-loopback binds the generated ephemeral token satisfies those guards, so the refusals are loopback-only. Completes the R1-3 contract sweep from the PR review. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): keep the loopback Host gate ahead of pre-auth health The access-log/trace move for the origin wall initially left hostAllowlist below the pre-auth health routes, dropping the DNS-rebinding defense from /health on loopback binds. Final chain: access-log, trace-id capture, hostAllowlist, same-origin strip + credential check, allowOriginCors, pre-auth health. Pre-auth health therefore sits below the origin wall (matched cross-origin probes keep their CORS headers) and below the access log, so liveness probes are logged — the unavoidable price of logging the origin wall's rejects. Adds a regression test pinning the Host gate ahead of pre-auth health and syncs the auth reference diagram. * fix(serve): address round-2/3 review — bound-address print modes, gate pins, doc contracts Quickstart now classifies by the address the socket actually bound (loopback binds print token-only or stay silent), normalises the IPv4-mapped wildcard, and skips software-interface ULAs; the boot gate passes the bound address and a broken-pipe guard keeps a vanished pipe reader from taking the listening daemon down. Web Shell gate clamps Retry-After at 30s; e2e barriers exclude the gate root; self-origin, access-log mount position, and fast-path graph pins replace regex enumeration. Bootstrap middleware order matches the runtime chain and the daemon doc set is swept to the same contracts. * docs(serve): name the Local Control Host gate in the chain diagram label * test(web-shell): pin the Retry-After floor and garbage-value edges * test(serve): pin the explicit-bind spelling against the bound address in full mode * docs(serve): keep the open-with-auth credential sentence from reading as tokenless non-loopback * fix(serve): address round-4 review — spelling-keyed generation carve-outs, dead-branch cleanup, missing pins Docs now name the localhost-resolves-off-loopback carve-out everywhere generation is described, the protocol reference states the same-origin exception ahead of the CORS wall, and the empty-hostname dual-stack claim is gone. Code drops the provably dead boundFamily branch and OR operand, scopes the proxy remedy to the plain-HTTP case, resets the gate live region at probe start, and updates the embedded-caller token JSDoc. New pins: resolved-token boot guards, bootstrap same-origin window, inet_aton bound address, web flag, EPIPE guard, boundAddress normalisation, TLS print path, no-candidate fallback, re-parse guard, GET/HEAD pre-auth gate, and the gate hint/status copy. * fix(serve): address round-5 review — blank-source and fragment-delivery contracts, pin premises Docs now state that any supplied-but-blank token source refuses without generating (not just the localhost carve-out), that --open fragment delivery keys on the resolved token regardless of flag, and that the same-origin exception authenticates only on the primary listener with the pre-auth shell routes exempt. The intermediary criterion is Host rewriting, not port translation, across the four doc lines and the boot stderr; webhook ingress joins the pre-auth enumeration; the --token "required when" cell lists all three loopback conditions; the embedded JSDoc names QWEN_DAEMON_TOKEN as the worker delivery channel. New pins: remote-HTTP(S) allow-origin boot arm plus loopback negative, plaintext warning positive assertion, non-canonical/unparseable/hostless/crossed self-origin rows, bootstrap premise assertions, stderr EPIPE arm, and a dedicated install-order file for the broken-pipe guard. * docs(serve): scope port-translation to non-loopback and narrow blank-token shadowing Port translation alone needs nothing only on a non-loopback bind; on the default loopback bind the DNS-rebinding Host allowlist rejects a translated port for every request including the shell document, and no --allow-origin overrides it. Blank-source shadowing is one-directional: a blank --token shadows a set env, but a blank env never shadows a non-blank --token, while blankness suppresses generation either way. The security reference's Overview bind invariant and quick-reference hostname row now state that a non-loopback bind always carries a bearer, with the two documented refusals named. * fix(web-shell): reset the gate only on operator-initiated probes The status-reset fix for the stale live region fired on every automatic retry too, flipping the submit button and re-announcing two strings once per cold-start cycle. An operator-probe ref now marks manual submits and the initial mount; automatic retries leave the transient message and an enabled button in place, so a manual retry can genuinely jump the queue. The suite also gains the documented contract rows: HTTP-date ceiling clamp, body.code-beats-Retry-After precedence, no auto-retry on 401/403, destination baseUrl and enterToken copy in both languages, typed-submit trimming, theme palette classes, and manual-supersedes-armed-retry. * fix(serve): settle the round-7 deferred list — scoping claims, boot pins, refusal message The non-loopback bind refusal now says an explicitly blank --token or QWEN_SERVER_TOKEN counts as no token, so both surviving causes get advice they have not already followed. The access-log exemption is documented as exact-path (GET /health and POST */heartbeat), with HEAD /health and GET /health/ named as logged, in the server comment and both doc tables. #token= delivery is described as keyed on a resolved token; "loopback binds never generate" becomes "loopback spellings" in all three spots; the --token "Required when" cell names the blank-value refusal; the pre-auth exception enumerations add loopback /health. New pins: the bootstrap Host gate ahead of the CORS wall via a raw-socket rebinding probe, the wildcard-bind premise read from server.address() instead of the echoed option, and tmpDir workspaces for the wildcard boot tests so the checkout is not registered. * fix(serve): charge pre-auth gate rejects to their own access-log budget The walls moved above the access log to make their rejects auditable, but that put the operator's own request lines behind a credential-less flood: ~30 s of >2 req/s rejects drained the shared 60/2-s burst and held authenticated traffic suppressed behind the aggregate warning. Host-allowlist, CORS-wall, and remote same-origin credential rejects now stamp a res.locals marker and draw from a separate 30/1-s budget, so a reject flood coalesces loudly while operator lines keep their own budget; the marker clears when a same-origin bearer verifies. Pinned at the middleware level (65 marked rejects leave the operator line logged, 30 individually logged, aggregate warning still fires) and at the app level (a 65-request wall flood cannot starve a subsequent authenticated request's line). * fix(serve): tolerate shim responses without locals when stamping the reject marker * fix(serve): isolate the pre-auth reject accumulator and stamp the null-origin arm The shared suppressed accumulator's flush spent an operator token per marked reject once the reject budget drained, moving the starvation boundary from ~2 rejects/s to ~3-4/s instead of eliminating it. Each budget class now owns its accumulator, flushes from its own tokens, and the shutdown seal drains both; the null-origin CORS arm joins the stamped reject set. Docs: the bootstrap paragraph names the delegating wrapper (only the three bootstrap paths go unlogged), the third port-translation spot gets the bind qualifier, the bearer 401 budget is named as unchanged, the localhost carve-outs note that any resolved token source satisfies the refusal, generation is described as spelling-keyed everywhere, and the empty-value clause names the loopback trusted-mode outcome. --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
ada633d584
|
fix(external-context): Align deletion responses with Mem0 SDK (#11397)
* fix(external-context): Align deletion responses with Mem0 SDK Accept successful HTTP responses and parse JSON without fixed acknowledgement rules, retaining exact post-delete absence verification. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(external-context): Clarify deletion confirmation and cover success statuses Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
b492ba8b41
|
docs(serve): state what --no-web actually removes (#11446)
The API-only docs claimed --no-web does not narrow the REST/SSE API. It does: POST /workspace/local-control/enable is registered unconditionally but receives webShellAvailable=false, so it fails closed with 409 local_control_web_shell_unavailable on every platform, and on macOS liveVoiceSurfaceAvailable requires serveWebShell !== false, so the /live/* routes, the /live/host WebSocket and the experimental.liveVoice.* settings keys are never registered. Say what the flag removes instead of denying it. Also point the daemon index at the embedding boundary that replaced page 20's deleted invocation recipes. Follows up the review comments on #11314, which merged before they were posted. |
||
|
|
d8c505bf42
|
docs(nav): stop hiding the developer examples section (#11426)
`examples/daemon-client-quickstart.md` is the only end-to-end walkthrough of
the daemon HTTP API, and
|
||
|
|
7bf0cafab4
|
feat(web-shell): separate Plan workflow from execution permissions (#11423)
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com> |
||
|
|
dd4cd4a08b
|
refactor(serve): decouple workspace capacity policies (#11428)
* docs: record workspace capacity measurements and rollout plan Record the controlled capacity baseline and separate registration expansion from optional runtime dormancy and LRU work. Document the P0 policy owners and their unchanged compatibility boundaries. Refs #11386 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * refactor(serve): decouple workspace capacity policies Give registration admission, modeled child capacity, and channel-control timeouts independent defaults while preserving the public legacy export. Cover the removed coupling and the SDK timeout override boundaries. Refs #11386 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
cf987cdc59
|
feat(dingtalk): present tool permission requests with native interactive cards (#10457)
* to #10388 [feat] Present DingTalk permission requests as cards Co-authored-by: Codex Using gpt-5.6-sol * to #10388 [fix] Localize DingTalk permission cards * fix(channels): handle permission response races * fix: preserve zh always-allow scope suffix and stabilize gate test legs (#10457) - ChannelBase: translate the stock prefix of scoped always-allow labels but keep the command/tool scope suffix (exec/mcp confirmations always generate suffixed names, and zh channels dropped them) - packages/cli: slow-host test budgets now also key on measured host saturation (isSlowTestHost), because the review-address verification gate re-runs tests through an env -i child that does not carry RUNNER_NAME; raise the dev-entrypoint startup budget accordingly - packages/cli: llm/AppContainer/live-serve tests point HOME at a scratch directory; the gate inherits a HOME the test process cannot write to, which crashed best-effort ~/.qwen writes Adds mutation-witnessed tests for permission-card controller guards (question_id, checkbox payloads, hasBusinessPayload gate, isCancel, requestId action, per-actor forbidden dedupe) and the zh user-scope always-allow branch. * revert(cli): restore RUNNER_NAME-keyed vitest config (#10457) The verification gate rejected the previous commit because packages/cli/vitest.config.ts is test-config machinery this PR never touched. Restore that file exactly to its pre-round state; the rest of the rejected commit (the zh always-allow scope-suffix fix and the per-file test accommodations) is preserved. The environmental gate-leg blocker the reverted change tried to work around — the gate's env -i clean child stripping RUNNER_NAME and QWEN_HOME while passing through a HOME the tests cannot write to — is escalated to the maintainer in the round summary instead. * fix(channels): attach the expiry denial handler at creation (#10457) A timed-out permission card fired respond('deny') and then suspended on the terminal card projection, leaving the denial promise without a handler for the whole window. When the bridge rejected inside it, the rejection escaped as a process-level unhandledRejection before the trailing catch ever attached, and Node logged a second rejectionHandled warning once the late await picked it up. Attach the handler where the promise is created so the expiry denial is reported as soon as it fails. The claimed-state assignment stays ahead of the call, and respond still precedes the projection. * test(cli): stop deleting the AppContainer scratch HOME under in-flight work (#10457) AppContainer's mount effect runs the real config.initialize() in an un-awaited IIFE, so extension-store lock work against this suite's scratch HOME can still be queued when afterAll runs. Deleting the tree there fails that work with ENOENT, which withLock rethrows as ExtensionStoreBusyError into a promise nobody awaits: all 171 tests pass and the packages/cli leg still exits non-zero on one unhandled rejection. Leave the tree for the OS to reclaim. Deferring the deletion to a process 'exit' handler was measured first and is not an option here — vitest workers in this repo never run it. * style(core): format Anthropic SSE retry test --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
a7a6addd4e
|
feat(web-shell): add opt-in browser task notifications (#11398)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
eded8817a7 | fix(web-shell): recover from initial workspace discovery failures | ||
|
|
cfb173ec46
|
refactor(channels): remove obsolete block streaming (#11381)
* refactor(channels): remove obsolete block streaming * docs(channels): explain the seams the block-streaming removal left behind Follow-up review cleanup on top of the block-streaming removal. No behavior change — comments and one test suite name only. - DingTalk's deliverBackgroundReply override is now body-identical to the base seam, so it reads as redundant. It isn't: the background aggregation flush passes prepared/failOnHttpError, which the base signature does not declare. Say so at the override. - The Feishu "no card session" branch comment lost its blockStreaming half and was left mid-sentence. Name the gate that actually reaches it. - The renamed block-streaming suite collided with the existing 'response delivery' describe, so the file had two identically named suites and a -t filter matched both. Give the second one a distinct name. * test(channels): preserve delivery regressions --------- Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
7eeeccea29
|
fix(cli): cap the background-notification queue and tell the model what was dropped (#11351)
* fix(cli): cap the background-notification queue and tell the model what was dropped The unified notification queue in the interactive TUI was an unbounded array: all six producers pushed without checking its length, and it was only ever cleared on a session switch. A noisy producer could therefore accumulate an arbitrary backlog that a single idle edge then fed into one model turn. The interim-monitor cooldown added in #10848 limits how often pulses start a turn, but pulses still pile up unbounded inside the cooldown window. Extract the admission rule the ACP Session already had into `background-notification-queue.ts` so both front ends share one cap (20) and one eviction order, and record every loss instead of writing it only to a debug log. Eviction prefers the oldest interim monitor pulse — the monitor's next poll supersedes it — and otherwise takes the oldest unprotected entry. The TUI protects agent results, workflow results and cron prompts, none of which have a second copy anywhere; when every queued entry is protected the incoming notification is dropped instead. ACP keeps its existing protection rule (entries continuing the todo-stop-guard work chain) and its existing order: it filters interim pulses before they are queued, so pulse priority is inert there. Losses are reported once, on the next drained turn, as a single summary line naming how many notifications went and which tasks they came from — per-loss lines would reproduce the flooding the cap exists to stop. The summary is parked if that turn is rejected, so a failed admission cannot lose the only record of what was discarded. * fix(cli): let the admission rule read the arriving notification Review follow-up on the review of 866163005a. `decideNotificationAdmission` never read its `incoming` parameter, which told the next reader the arriving item mattered to the decision when it did not. Neither tsc nor ESLint flags it — `noUnusedParameters` is off and the rule's default `args: after-used` ignores a parameter followed by a used one — so the signature would have shipped misleading. Give it the meaning it should have had: when the queue holds no interim pulse to evict and the arriving notification is itself a pulse, drop the newcomer rather than displace a terminal result. A pulse is superseded by the monitor's next poll, so evicting the only copy of a shell result to make room for one trades the wrong way — the same reasoning that already puts queued pulses first in line. ACP is unaffected: it filters pulses before they are ever queued, so an arriving item there is never interim. Also from the review: - Project the ACP queue for the admission decision, not just for the tally. `interim` exists only on the projection, so passing raw entries would have silently disabled pulse priority for whoever relaxes the ACP monitor filter, which is exactly what the projection's docstring promised would keep working. - Reattach the `QueuedNotification` docstring, which had been left stranded above `PendingDroppedSummary` when the two interfaces were reordered. - Record at the call site why the ACP does not park the summary across a refused turn the way the TUI does: past admission the summary shares the notification's fate, and that path does not re-queue the item either. - Stop the docs claiming "nothing is lost silently". The drain prunes pulses from cancelled monitors and can empty the queue before the summary is taken, so the summary rides along with whatever notification drains next and is discarded unreported if the session is cleared first. * fix(cli): preserve notification overflow semantics * fix(cli): preserve notification overflow reporting --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com> |
||
|
|
eb383fd3bb
|
feat(core): enable the built-in web_search by default on ModelStudio Standard/Token Plan (#11348)
* feat(core): enable the built-in web_search by default on ModelStudio Standard/Token Plan web_search was opt-in and needed enabled=true, a search model selector, and a modelProviders entry with an envKey even though a ModelStudio user's primary entry already satisfies every condition. Declare a dashscope search backend on the Standard API Key and Token Plan presets and let the registry derive the search side channel from the primary model's entry when nothing is configured explicitly. Entries matching no preset are adopted when their host is a DashScope endpoint, covering hand-written entries and the workspace-specific Token Plan hosts; Coding Plan endpoints stay out until they are verified. Explicit model/env configuration keeps working for any DashScope-compatible entry. Auto derivation fails silently so users on other providers see no notice, enabled=false (or ENABLE_WEB_SEARCH=false) turns the tool off, and bare/safe mode always does. * fix(core): derive the env-only web_search backend from the resolved generation config The auto path read the runtime model snapshot, which is empty when the tool registry is built — detectAndCaptureRuntimeModel() runs after createToolRegistry() — and which only carries apiKeyEnvKey when a modelProviders entry supplied it, so a pure OPENAI_BASE_URL / OPENAI_API_KEY setup never registered web_search. Read the resolved generation config instead, which the ModelsConfig constructor already has, and fall back to the auth type's default key variable when no entry named one. Also correct the docs: the default Auto approval mode lets the classifier approve searches without prompting, and a Custom Provider entry on a DashScope host is adopted like a hand-written one. * fix(core): keep a failed web_search backend derivation from breaking the tool registry Reading the generation config to derive the search backend assumed a complete Config surface, so a test double that stubs getModelsConfig() without getGenerationConfig() threw while the registry was being built and took every other tool down with it. Derivation is opportunistic: an unexpected Config shape should cost web search alone, so catch and fall back to the silent no-backend result. Give the subagent-manager double the accessor it was missing. * chore: re-trigger CI The Qwen Code CI workflow was not scheduled for 0149317b95 (every other workflow on that commit ran); this empty commit re-runs it. * fix(core): address web search review findings --------- Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com> |
||
|
|
88b0afaeb1
|
docs: require bilingual design documents (#11419)
Co-authored-by: probe <probe@local> |
||
|
|
c069801657
|
feat(workflows): journal failed agents and settle agent failures to null (#11196)
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 / 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 / Real daemon E2E / Java 11 (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Blocked by required conditions
* feat(workflows): journal failed agents and settle agent failures to null A workflow's journal recorded that an agent started and that it returned, and nothing in between. A key with a `started` and no `result` could be an agent that failed or a run that was interrupted with that agent in flight, and a resume had no way to tell the two apart — it re-ran both and said nothing about either. The same gap ran through the script contract. A `parallel()` slot whose agent hit its turn cap became `null`, while the identical failure in a bare `await agent()` threw and ended the run, so the shape of the call decided whether one broken agent cost a slot or the whole workflow. Agent-level failures — turn cap, time cap, model error, no structured result, the user stopping that agent — now settle to `null` in both forms and are journaled as `failed`. Run-level conditions still throw: the token budget, the agent cap, a stall that survived every attempt, a cancelled run. A cancelled run writes no `failed` records at all, so its open keys read as interrupted rather than broken. What failed is now reported rather than counted: the run trailer and the background completion notification list each failed agent with its error, capped with a named remainder, and a resume says which calls it is re-running and why. The run event carries agents_failed, agents_cached and agents_respawned. Part of #11013 * fix(workflows): only report a respawn for a call that never finished The respawn report fired for every call that ran live on a resume, not only for the ones the journal had left unfinished. Once any call misses, the prefix invariant sends every later call live too — including calls that completed last time — so an ordinary interrupted fan-out reported its already-finished agents as interrupted alongside the one that really was, and counted them. A key with a journaled result is now excluded: it is re-running because of what happened upstream of it, which is a fact about the run rather than about that agent. Part of #11013 * fix(workflows): align failure settlement and resume reporting * fix(workflow): preserve run failure semantics * fix(workflows): journal starts only after dispatch admission --------- Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
bdfa63613a | fix(web-shell): expose known capabilities on initial connection | ||
|
|
a5adf4f91c
|
Merge branch 'main' into codex/fix-web-shell-cold-session-loading | ||
|
|
0bfc1bbd3b | fix(web-shell): avoid duplicate cold session restoration | ||
|
|
1f890086f1
|
feat(goal): carry budget figures and progress guidance in the continuation prompt (#11257)
* feat(goal): carry budget figures and progress guidance in the continuation prompt The continuation prompt told the model what the objective was and how to deliver it, and nothing about two things it had no other cheap way to know. It could not see how much of the spend window was left. A Goal stops when `tokensUsed` reaches `tokenBudget`, 30,000,000 by default, and gets one wind-down turn to hand off; until that turn arrives there was no signal at all, so the model could not tell turn 3 of a long run from the turn before the budget stops it, and could not choose between opening a broad investigation and finishing what it had. `get_goal` does not carry the figures either, so there was not even an expensive way to ask. It was also never asked whether its last turn accomplished anything. The verifier only ever sees a terminal proposal, so a turn that proposes nothing is judged by nobody -- and a turn spent restating status is exactly the turn that proposes nothing. Each runtime-scheduled turn now opens with what has been spent, out of what, what remains, and how many turns are behind it, followed by four standing lines: treat the workspace rather than the conversation as authoritative; work toward the end state the objective asks for rather than a more easily reached one; judge whether the previous turn actually changed anything before spending this one; and check every requirement against citable evidence before proposing completion. The figures sit after the standing objective guard and before the objective-updated notice, which is about what changed since the last turn and so reads last. They stay out of the data block on purpose: that block is compared by content to decide whether the objective changed, and a number that moves every turn would make every turn look like an edit. The remainder is clamped at zero, since the hand-off turn runs with the window already overspent. The progress lines are skipped on that hand-off turn, which is told not to start new work -- a line asking for "a different concrete action now" would contradict it. The budget line stays, because a hand-off reports the numbers it stopped at. `usage` is optional on the host contract, so a host with no figures renders exactly the prompt it did before. The three hosts copy it through their queue entries alongside the fields they already copy. * test(goal): pin the budget figures through each host, and skip the judgement on turn one The three host copies were the only link in the chain with nothing behind them: `usage` is optional on both sides of every hop, so deleting a copy typechecks and costs the prompt its budget line on that host alone. One case per host now fails when its copy is removed. The judge-your-previous-turn line is held back on the Goal's first turn. `create` schedules a continuation before any turn has finished, so that line asks the model to judge a turn that does not exist. A host that reports no figures still gets the line: not knowing the turn number is not evidence of a first turn. * fix(goal): satisfy formatting checks * fix(goal): address continuation prompt review * refactor(goal): centralize continuation payload --------- Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
70cf363395
|
feat(web-shell): Improve split-view session navigation (#11250)
* feat(web-shell): Improve split-view session navigation * fix(ci): Run Web Shell browser checks on hosted runners * fix(web-shell): Keep split navigation outside approval shortcuts * fix(web-shell): Address split-view review regressions * fix(web-shell): Stabilize split approval reports * fix(web-shell): Preserve history anchors during slow rendering --------- Co-authored-by: 易良 <1204183885@qq.com> |
||
|
|
562ea5a0f0
|
fix(docs): stop daemon capability totals drifting (#11370)
* docs(serve): sync the daemon capability tag count with the registry Two capability tags landed on main in succession and only one of them bumped the documented count. The worktree-reset tag came with the index update, taking the documented total to 158; the extension-activation refresh tag arrived afterwards and left the document alone, so the registry now holds 159. Each PR was green against its own base — the second one branched before the first landed — and the contract test only sees the mismatch once both are on main. The count is asserted against the registry rather than hand-maintained, so this is a one-character correction with the test as its own verification: red before, green after. * fix(docs): remove volatile capability totals --------- Co-authored-by: Shaojin Wen <szujobs@gmail.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
6b7e5615c7
|
fix(web-shell): show session active work (#11267)
* fix(serve): report child-owned session turns as active work * fix(web-shell): show session active work * fix(web-shell): gate todo spinner on live work * chore(web-shell): format sidebar after merge * chore(web-shell): use repository formatter * fix(acp): retain active work during session registration * fix(web-shell): clarify active work archive warning --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
7023bba760
|
feat(cli): OpenTUI parity closeout (dialogs, composer, shell mode) (#11152)
* fix(cli): render the OpenTUI confirmation as one surface and bound the transcript flood
The mem0 confirmation scenario exposed three stacked gaps in the OpenTUI
renderer: the awaiting-approval path never reached the transcript model,
the expanded dialog body overflowed a fixed alt-screen viewport, and the
confirming card duplicated the payload the dialog already carries.
Emit confirm/confirm-resolved transcript events from the real scheduler
awaiting path, render the expanded body as a tail window budgeted against
the terminal height (alt-screen has no scrollback to fall back to), let
the pending card yield its description to the dialog, and cap every tool
card description to a bounded head with ink's own hidden-tail vocabulary.
A shared config.initialize() once-guard stops submit-time "Chat not
initialized" races, and a batch where every call was cancelled now ends
the turn without a follow-up model request, matching the ink stream.
The mem0 e2e post-approval wait moves to request bodies so it does not
depend on raw pty bytes; both legs pass all five scenarios.
* fix(cli): auto-open the OpenTUI auth dialog at boot (U-6)
Ink's boot auto-open is two triggers: no auth type configured (useAuth
initial state) and the one-shot startup authError
(useInitializationAuthError); shouldOpenAuthDialog is dead in production
code. The OpenTUI entry now computes that request once and seeds the
shell's dialog state with it, and the auth dialog surfaces the startup
failure through its existing error surface. Also repairs the entry
fallback-contract tests, which had been failing since
|
||
|
|
bb79319ce5
|
docs(serve): sync the daemon capability tag count with the registry (#11368)
Two capability tags landed on main in succession and only one of them bumped the documented count. The worktree-reset tag came with the index update, taking the documented total to 158; the extension-activation refresh tag arrived afterwards and left the document alone, so the registry now holds 159. Each PR was green against its own base — the second one branched before the first landed — and the contract test only sees the mismatch once both are on main. The count is asserted against the registry rather than hand-maintained, so this is a one-character correction with the test as its own verification: red before, green after. |
||
|
|
d7b36db889
|
feat(core): expand ${session_id} in per-provider customHeaders (#11282)
* feat(core): user-configurable session-ID header for trusted hosts
Gateways like OpenCode Go reject requests without a stable
per-conversation header (x-opencode-session, enforced since 2026-09-06),
and customHeaders cannot carry runtime-dynamic values — the template
route was reviewed and rejected in the outbound-propagation design
(§12.7), which pre-specifies this setting instead.
Adds outboundCorrelation.sessionIdHeader { enabled, headerName,
trustedHosts }: default off with an empty host allowlist, HTTPS-only
exact-host matching, header-name token validation (an invalid name
skips the user branch and keeps the built-in first-party one), and the
enabled flag re-checked at the send site so the path fails closed. The
value resolves per request via the existing wrapFetchWithSessionId
seam, so /new and /resume rotate it without rebuilding the client.
The built-in Routify allowlist behaviour is unchanged.
Closes #10995
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(core): keep the built-in session_id header independent of the opt-in branch
`buildSessionIdHeaders` called `getOutboundSessionIdHeaderSettings()`
before the built-in short-circuit and inside the shared `try`. A `Config`
collaborator without that method threw, the catch swallowed it into a
debug warning and returned `{}` — so the first-party Routify `session_id`
header that ships today was dropped too. CI caught it: 7 failures across
the four provider suites that build partial `as unknown as Config`
doubles (`AssertionError: expected null to be 'test-session'`).
Rather than teach those four suites about a setting they do not use, make
the branches independent by construction: the opt-in branch now resolves
through `configuredSessionHeaderName()`, which cannot throw. A setting
that ships off is structurally unable to alter the path that ships on.
Also in the same seam:
- Re-check the flag as `enabled === true`, not `enabled !== false`. The
old test treated a settings object with no `enabled` field as ON, which
is fail-open — the opposite of what the comment claimed to guarantee.
- Match the trusted host before validating the header name, and warn once
per distinct name. An invalid name previously logged on every outbound
HTTPS request, including ones to hosts that were never listed.
- Collapse a configured name that differs from `session_id` only by case,
since header names are case-insensitive on the wire.
Docs: `docs/design/2026-09-03-outbound-session-id-header.md` still said
the behavior is "intentionally not configurable" with "no user-configurable
allowlist". Updated, and given the threat-model section that §12.7 of the
outbound propagation design asked this follow-up to bring: recipient set,
de-anonymization window, redirect forwarding, misconfiguration, and why a
per-request UUID is out of scope. `settings.md` gains the redirect,
punycode and debug-log caveats; the `headerName` JSDoc no longer
contradicts the regex it describes.
Tests: the built-in header surviving a Config without the getter, an
absent `enabled` field treated as off, and the case-only-difference
collapse.
Not run locally (this box cannot): typecheck, build and vitest are CI's
to confirm. ESLint passes on the changed files.
* feat(core): expand ${session_id} in per-provider customHeaders
Replaces this PR's implementation. The previous approach added a global
`outboundCorrelation.sessionIdHeader` with its own `trustedHosts`
allowlist; this delivers the same user need through the mechanism #10995
actually asked for.
Why the change of approach:
- **`outboundCorrelation` is the wrong home.** It exists (from #4390) for
correlation data *qwen-code itself* injects, is documented only in the
developer telemetry docs, and defaults everything to off because it is
optional. #10995 is the opposite on all three counts: the user
configures it, they need it in the settings docs, and without it the
gateway rejects every request. Filing it there repeats the category
error #4390's review objected to, one level down.
- **A single global `headerName` cannot serve two gateways.** One string
and one host list mean a user with two gateways wanting different
header names cannot express it, and two model entries on the same host
cannot be distinguished at all.
- **`trustedHosts` re-encodes `modelProviders[].baseUrl`.** A global
setting has no scope of its own, so it has to rebuild one from the
endpoint the user already wrote down.
`customHeaders` is already per-provider, so scope comes for free: which
hosts may receive the value is answered by the provider entry the header
hangs on, and which providers need it by which entries carry it. No
allowlist, and none of the host normalization, punycode conversion or
wildcard rejection it required.
What is kept from the #4390 review: the consent decision stays in
`outboundCorrelation`, as `allowDynamicHeaderValues` (default false),
because an expanded value carries live session state to a third party.
It also stops a provider preset or extension from quietly promoting a
`customHeaders` entry it ships into an identity header — provenance is
lost once presets and user settings are merged.
Fail-closed throughout: gate off, empty session ID, or a Config that
cannot answer all drop the header rather than putting a literal
`${session_id}` on the wire. Whether any placeholder exists is decided
once at client construction, so providers without one keep the fetch
wrapper's existing early return. Gemini needs its own path: its
customHeaders are frozen into the SDK client options at construction, so
placeholder-bearing entries are kept out of the client entirely and
supplied per request instead.
The built-in first-party Routify branch is untouched, and
docs/design/2026-09-03-outbound-session-id-header.md stays accurate as
written — the two mechanisms are complementary, which is how #10995
described them.
Verified locally: 421 tests pass across the six affected core suites,
including the four provider suites whose session_id assertions the
earlier approach broke. Typecheck, build and the settings-schema
regeneration are CI's to confirm — the schema entry was hand-written with
its description extracted from settingsSchema.ts, so CI's drift check is
the authority.
Rationale and the comparison in full:
docs/plans/2026-09-07-session-id-header-shape-comparison.md
* docs: drop the shape-comparison plan from this PR
The rationale it carried is already in the PR description and the
implementation commit message. The rest of it was review archaeology that
does not belong in the repo.
* fix(core): warn when a configured placeholder is refused by the gate
#10995 treats writing `${session_id}` into a provider entry as the opt-in
("the header is only sent when the user configures it"). With the consent
switch defaulting to off, a user following the issue's example got
silence: the header was dropped and the only trace was a debug log that
is normally disabled. Their observable symptom would be a gateway
rejecting every request with nothing on screen to explain it — the same
failure mode this PR's earlier approach was criticized for.
Warn on the console at client construction instead, naming both the
header and the setting to flip, once per distinct header set. The check
cannot throw: it runs while a provider client is being built, and a
partial Config must not break that.
Docs now lead with the fact that setup is two steps, and say what the
warning looks like.
Also adds the end-to-end coverage that was missing: expansion through the
seam the providers actually construct, the gate dropping the header
rather than emitting the literal, rotation across sessions without a new
client, and a provider with no placeholder keeping the untouched
early-return path.
428 tests pass across the six affected core suites.
* fix(core): close dynamic header consent gaps
* fix(core): close remaining dynamic header gaps
* fix(core): gate session environment aliases
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: 易良 <jinjing.zzj@gmail.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
|
||
|
|
59163a3ad0
|
docs(serve): clarify API-only HTTP integration (#11314)
* feat(serve): add --api-profile and an OpenAPI contract for REST integrators
External teams want to build on Qwen Code over HTTP without the Web Shell.
`--no-web` already gives them an API-only daemon, but two gaps remain: the
surface they get is the whole ~110-route set the Web Shell drives, and there
is no machine-readable contract for any of it.
Add `--api-profile=full|minimal`. `full` (default) is unchanged and installs
no middleware at all. `minimal` serves only the partner-facing subset --
session lifecycle, prompting, SSE, permission responses, and read-only
workspace context -- and answers 404 elsewhere.
Implemented as a single gate after the `authenticate` middleware rather than a
conditional on each of the ~65 route registrations: the goal is narrowing the
authorization and contract surface, not saving startup work, so routes stay
registered and simply become unreachable. Placing it after authentication
keeps the 401 uniform, so the enabled surface cannot be mapped by diffing 401
against 404.
The motivation is least privilege as much as ergonomics. Every route shares
one bearer token, so under `full` a leaked token reaches `/workspace/trust`,
extension install, `/workspace/git/push` and `/workspace/settings`. `minimal`
removes those from the routable set entirely.
Also:
- `docs/developers/qwen-serve-openapi.yaml` describes exactly the `minimal`
surface, with a bidirectional drift guard so the two cannot diverge.
- `/capabilities` reports `apiProfile`. Under `minimal` the "tag present means
behavior present" invariant does not hold, since `features` still lists
every tag the build supports; documented as an explicit carve-out, with the
spec as the authority on reachability.
- Open a `./serve` subpath export. `serve/index.ts` was already written as a
public barrel for external embeds; the export map just never exposed it.
- `docs/developers/rest-api-integration.md` for integrators, including the
fact that the daemon spawns `qwen --acp` children and therefore needs the
CLI on its host.
Plan: docs/plans/2026-09-08-serve-api-decoupling.md
Not verified locally (memory-constrained box): no build, typecheck, or test
run. Lint and formatting were checked on the touched files; the rest is CI's
to confirm.
* fix(cli): type the api-profile gate mock as NextFunction
vitest's Mock<T> collapses NextFunction's overloaded call signatures to
the last one, so `vi.fn<NextFunction>()` produced a Mock that tsc refused
to pass where a NextFunction was required:
api-profile.test.ts(38,68): error TS2345: Argument of type
'Mock<NextFunction>' is not assignable to parameter of type
'NextFunction'.
That broke `tsc --build`, which runs inside the install/prepare step, so
every CI job on the PR failed at "Install dependencies". Use the same
`vi.fn() as unknown as NextFunction` shape the other serve tests use.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-conflict/jmtrj6hmrpn
* fix(ci): satisfy yamllint and register --api-profile in the fast-path guard
`docs/developers/qwen-serve-openapi.yaml` landed in unquoted flow style, which
the repo's yamllint config rejects: 284 `quoted-strings` errors plus 90 `braces`
errors. Requote every plain string scalar and expand the non-empty flow
mappings, matching the style the other non-workflow YAML here already uses
(`packages/live-host/electron-builder.yml`,
`packages/core/src/skills/bundled/computer-use/agents/openai.yaml`). The parsed
document is unchanged, so the `api-profile.test.ts` drift guard reads the same
contract, and Prettier still reports the file clean.
`--api-profile` is a new yargs serve long option that the fast path does not
mirror, so it falls back to the full parser as designed. Register it in the
completeness guard's sample argv and in the fallback set, which is what the
guard exists to make explicit.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-conflict/jmtrlbneopr
* docs(serve): clarify API-only deployment
* Revert "docs(serve): clarify API-only deployment"
This reverts commit
|
||
|
|
ab33974509
|
feat(external-context): Add daemon memory deletion (#11337)
Add an opt-in workspace-bound deletion profile with exact target reads, full-text and scope verification, single-shot deletion, and absence checks. Preserve full record identifiers and cover approval and failure boundaries. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
272de8ace6
|
fix(serve): configure live-state polling with a five-second default (#11339)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
cffc40495a
|
feat(dingtalk): show dynamic lifecycle tags (#10504)
* to #10366 [feat] Show DingTalk dynamic lifecycle tags Co-authored-by: Codex Using gpt-5.6-sol * to #10366 [feat] Show lifecycle phases on DingTalk status cards * to #10366 [feat] Show lifecycle phases in DingTalk card body * to #10366 [feat] Harden DingTalk lifecycle presentation - Keep lifecycle cards phase-only and localized - Coalesce stale reactions and prioritize terminal cleanup - Resolve the effective language before channel construction Co-authored-by: Codex Using gpt-5.6-sol * to #10366 [bugfix] Close DingTalk lifecycle delivery races - Preserve terminal and latest phase updates across rejected reaction calls - Keep source labels and current phases stable in truncated status cards - Await bounded reaction cleanup before channel shutdown Co-authored-by: Codex Using gpt-5.6-sol * to #10366 [bugfix] Preserve phases across reaction drain handoff Reschedule lifecycle reaction draining when a phase arrives during drain teardown. Cover the no-op drain microtask race and remove an unrelated formatting diff from the PR. Co-authored-by: Codex Using gpt-5.6-sol <noreply@openai.com> * feat(dingtalk): refine tool lifecycle phases * test(dingtalk): cover granular lifecycle phases * fix(dingtalk): secure lifecycle phase updates * test(cli): remove duplicate folder trust mock * fix(channels): clear terminal tool kind cache * fix(channels): await DingTalk reaction drain at shutdown and harden drain paths (#10504) - DingtalkChannel.disconnect() is synchronous again; the reaction-cleanup drain is surfaced through waitForDisconnect() so disconnectChannels() actually awaits it at shutdown instead of exiting with transient tags still attached. - Repair the two vacuous shutdown tests to hang their pending promise off the waitForDisconnect hook production awaits. - AcpBridge heartbeat filter now also drops subagentProgress frames, matching the DaemonChannelBridge guard so subagent progress no longer resets the phase to Working. - DaemonChannelBridge restores the remembered kind for kindless terminal tool updates (per-session map, retired on terminal status and session drop), so daemon-mode tool completions emit lifecycle events instead of protocol errors. - finishReactionState keeps the state when a terminal recall fails and finishReaction re-arms the blocked finish, giving the cleanup a second chance instead of stranding a stale phase tag. - Drain failures are latched per transition so a failing emotion API is not re-hit once per streamed chunk; a different desired tag still retries. - Correct the design-doc phase list and the two plan-doc status notes. * fix(channels): bound DingTalk reaction finish retries and retry failed terminal tags (#10504) * fix(channels): clear DingTalk transient state on standalone bridge exit (#10504) - Finish transient reactions without a terminal tag and terminalize ticking status cards when the standalone ACP bridge process exits, through a new ChannelBase onBridgeDisconnected hook wired to the bridge 'disconnected' event. Crash recovery restores the sessions on a fresh bridge, so session routing state is deliberately left untouched and no outcome is fabricated for the interrupted turn. - Rename the tool-failure presentation phase to 'failed' and label it '⚠️ Tool failed' / '⚠️ 工具失败': the runtime makes no retry, so the old '⚠️ Retrying' label claimed a recovery that never happens. - Restrict the Chinese presentation table to Simplified locales (zh, zh-CN) so zh-TW and other Traditional locales fall back to English instead of being shown Simplified labels. * fix(channels): wire DingTalk bridge-disconnect cleanup in production (#10504) The standalone bridge-exit hook was only attached when a channel managed its own bridge events; every production construction passes a router, so the cleanup never ran and a crashed turn kept its transient reactions and ticking status card forever. Dispatch the hook from the crash-recovery disconnect listener attached to every bridge, and localize the terminal card copy this path newly reaches for non-Chinese display languages. Also pin the zh label tables, display-language wiring, kind-map retention, and reaction routing arguments the review flagged as unverified. * docs(channels): drop disproved ordering claim from bridge-disconnect comment (#10504) The comment claimed the transient-state dispatch must run before the recovery guards can skip, but the guard's skip paths are all shutdown paths, where disconnect() performs the same cleanup; moving the dispatch after the guard is byte-identical on crash, SIGINT, and 403+crash probes. Keep the true why: cleanup is transient-only because crash recovery restores the sessions. --------- Co-authored-by: Codex Using gpt-5.6-sol <noreply@openai.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
f70aaced58
|
feat(goal): size the checkpoint verifier timeout for a full claim list, and let operators set it (#11305)
* feat(goal): size the checkpoint verifier timeout for a full claim list, and let operators set it The checkpoint verifier compresses an evidence window into up to 32 claims of up to 2,000 characters each, with sources, streamed with thinking off: tens of kilobytes of JSON, minutes at a large model's decode rate. Its 30 s ceiling fit a small window and abandoned every checkpoint of the one window compaction exists for, the one that had overflowed the catalog, so no checkpoint was ever written. Default to 180 s, derived from the output bound, and expose it as model.goalCheckpointTimeoutSeconds with the same validation shape as model.goalTokenBudget. * fix(goal): stream the checkpoint side query and review follow-ups (#11305) Address the review round on the checkpoint verifier timeout PR: - Stream the checkpoint verifier side query so the raised ceiling is reachable: non-streaming holds all bytes until the JSON is generated, so the provider request timeout (default 120 s) aborted and retried every attempt past it, leaving any ceiling above it unreachable. Streamed, the transport timeout bounds only connect + first response and the stream guards apply instead. - Declare minimum/maximum on model.goalCheckpointTimeoutSeconds so the /config write path refuses values the next startup rejects, and regenerate the committed settings.schema.json mirror (the freshness gate was failing CI's Lint & Static job). - Correct the three strings that claimed a timed-out checkpoint counts as stalled: it settles as an inconclusive check, the stall streak is preserved, and a later turn retries (the stall-counting behavior is companion PR #11304's change). - Pin the verifier arming call and the invalid-value debug warning with tests, including the silence half for unset/valid values. * test(cli): add GOAL_CHECKPOINT_TIMEOUT_SECONDS_CAP to partial core mocks (#11305) The new value import in settingsSchema.ts is evaluated at module load, so the two suites that hand-write a partial @qwen-code/qwen-code-core mock without spreading the original module crashed at collect time with "No GOAL_CHECKPOINT_TIMEOUT_SECONDS_CAP export is defined on the mock". Add the constant to both mocks, matching each file's existing pattern of hardcoded constants. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(goal): cap the checkpoint ceiling at a wait the default wire honours The checkpoint call is streamed, so past the stream lifetime guard it is the guard that ends the call and the verifier's own timer never fires. A cap of one hour therefore let the setting validate, and the getter report, a ceiling no default deployment can reach: an operator raising it to survive a slow model waited the lifetime cap, got no checkpoint, and saw exactly the behaviour they had before touching it. Derive the cap from DEFAULT_STREAM_MAX_LIFETIME_MS instead, and say in the setting, the schema and both docs that a longer ceiling needs the stream guard raised too -- a knob that lives in the environment rather than settings.json. * test(goal): pin the delay the checkpoint abort timer is armed with The suite proved the exported default is 180 s and that a timer fires when one is passed, but nothing observed the delay handed to setTimeout: clamping it back to the old 30 s left every test green while an operator's configured ceiling silently did nothing. Advance fake timers to one tick before the armed ceiling and assert the signal is still live, then over it and assert the rejection, for both the configured value and the built-in default. * docs(goal): say the checkpoint ceiling is fixed, not raisable by env var The cap is derived from DEFAULT_STREAM_MAX_LIFETIME_MS at module scope, but the env override is resolved per pipeline, so lifting QWEN_STREAM_MAX_LIFETIME_MS does not widen the accepted range: a value above 900 is still refused at startup. Five user-facing strings said the opposite -- the constant's docblock, the settings schema description, the validator's error, and both docs -- so an operator who raised the guard would retry the setting and be rejected again with advice they had already followed. --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
8f121742ac
|
refactor(daemon): decouple extension activation refresh (#10991)
* refactor(daemon): decouple extension activation refresh
* test(daemon): update activation capability expectation
* docs(daemon): clarify activation convergence window
* fix(daemon): correct activation docs and guard stale refresh message
* fix(cli): route turn-index reads through runtime-root pin (#11036)
(cherry picked from commit
|
||
|
|
32f51a330b
|
chore: restore regression coverage after WebUI retirement (#11101)
* chore(web-shell): follow up #9812 review findings and deferred suggestions Address the remaining review suggestions from the @qwen-code/webui retirement (#9812) and the deferred items tracked in #11076: - Fix the 01-architecture.md adapter diagram path (missing session/). - Correct the followup-suggestions doc: generation fires on end_turn, and headless/SDK daemon clients should opt out to avoid the per-turn LLM cost. - Pin playwright to 1.61.1 to restore version parity with @playwright/test. - Restore file: scheme rejection coverage in Markdown.test.ts. - Pin the historical sessionStorage key prefix with a literal assertion. - Cover the legacy exported JSONL rejection and ChatRecord happy path. - Restore the shell-substring classifier trap with a live path. - Point useDaemonFollowupSuggestion docs at ChatEditor, not the deleted InputForm. - Replace the theme-toggle timing-race assertion and exercise the unhandledrejection fail-closed path in the document browser gate. Closes #11076 * fix(test): poll the theme class instead of a matcher vitest lacks The browser gate imports expect from vitest and drives playwright's Locator directly, so toHaveClass does not exist on the assertion and typecheck:integration failed with TS2339 on this line. Poll the class attribute through the same expect.poll idiom the neighbouring assertions use, which keeps the retry the assertion was switched to and needs no @playwright/test dependency. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-conflict/jmtooas8sln * fix(test): assert the exported theme class exactly `document-main.tsx` toggles `dark` and `light` on <html> mutually exclusively and nothing else writes `documentElement` classes, so the attribute is always exactly one of them. Matching `/light/` as an unanchored substring would also accept `dark light`, which is the theme-toggle breakage this poll exists to catch. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtow5pgslz * fix(test): realpath the exporter main check and pin its JSONL guard Node realpath-resolves `import.meta.url` for the ESM main module but leaves `process.argv[1]` as invoked, so comparing the raw spellings made a symlinked invocation skip `main()` and exit 0 having written nothing — a silent no-op the harness reads as success. Compare realpaths instead and export the predicate so both directions are pinned. Also extract the input gate into `assertRenderableJsonl` so the legacy exported-JSONL rejection can be asserted directly: the previous tests only pinned the two predicates feeding it, so deleting the guard and losing the remediation hint kept every test green. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtow5pgslz * docs(followup): name the real ACP stop reasons, drop "unconditional" The parenthetical this PR added named `interrupted` as a daemon stop reason. The ACP `StopReason` union is `end_turn | max_tokens | max_turn_requests | refusal | cancelled`, so there is no `interrupted`; an interrupted turn reports `cancelled`. Name the four suppressing values the union actually defines so an integrator writing `stopReason === '...'` gets a branch that can fire. "unconditional" was wider than the code and contradicted the condition list on the same page: `Session.ts#maybeEmitFollowupSuggestion` returns early on the stop reason, the todo stop guard, `ui.enableFollowupSuggestions`, PLAN approval mode, a missing chat, and a non-model last history entry. Point at those conditions instead; the cost advice still holds because the `enableFollowupSuggestions === false` return sits before `generatePromptSuggestion` is reached. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtp6vibomd * test(web-shell): drop the duplicate historical-key assertion `persists under the literal historical key prefix`, added on this branch, became a byte-equivalent twin of `writes under the historical WebUI key`, which reached main with #11107 and arrived here through the merge at this PR's head: same `persistStableClientId('client-a', 'session-a')`, same literal `qwen-code-webui-client-id:session:session-a`, same expectation. One fact was reported under two names, so a prefix rename reddened two tests and an auditor could not tell which copy was load-bearing. The surviving test's comment already carries the rename rationale, so dropping the copy loses no coverage. Verified with `cd packages/web-shell && npx vitest run client/daemon/session/clientLifecycle.test.ts`: 19 passed before, 18 passed after. Mutating SESSION_CLIENT_ID_STORAGE_PREFIX to `qwen-code-webshell-client-id:session:` after the dedupe still reddens 3 tests (15 passed), including the surviving `writes under the historical WebUI key`; reverting the mutation returns to 18 passed. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtp6vibomd * test(integration): drop the unreachable unhandledrejection arm The block this PR added after the CDN-failure loop could not reach the `window.addEventListener('unhandledrejection', showLoadError)` handler its comment claimed to exercise. `page.route('**/*', route => route.abort('blockedbyclient'))` fails the `<script id="transcript-renderer">` load, the template's capture-phase `error` listener (document-index.html:45-56, matching `event.target.id === 'transcript-renderer'`) calls `showLoadError()` first, and `setContent(..., { waitUntil: 'load' })` only resolves after that subresource has failed - so `data-render-complete='error'` and the alert text were already in place before `page.evaluate` dispatched anything, and showLoadError's one-shot guard (`if (document.body.dataset.renderComplete) return;`, document-index.html:30) made the synthetic rejection a no-op. The block was a strict duplicate of the loop's own `rendererBody === null` iteration: identical all-abort route, identical two assertions. Exercising the handler for real needs a renderer body that passes the document's pinned SRI integrity and then rejects during boot; no body this gate can serve passes SRI, so the arm cannot be made honest here. Deleting it returns the file to origin/main's state for this test - the handler and its missing coverage are both pre-existing on main (packages/web-templates is not in this PR's diff), so no coverage this PR was responsible for is lost. Verified: `npm run typecheck:integration` reports 0 errors in this file (6 remain, all in packages/** against generated/dist paths absent from an unbuilt worktree: channels/feishu, cli/src/generated/git-commit.js, web-templates/src/generated/*); `npx prettier --check` and `npx eslint` clean on the file. The browser gate itself was not executed locally - it needs the full build chain plus Chromium. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtpfg5ewmp * ci(review-context): route concurrent-runner changes to the helper-tests lane This PR added `integration-tests/concurrent-runner/export-html-from-chatrecord-jsonl.test.mjs`, the first `node:test` file under `integration-tests/`, and wired it into CI through the `HELPER_TESTS` env list in `.github/workflows/ci.yml`. The tracked review-context manifest still mapped the whole `integration-tests/**` path to `recommendedTests: ['integration-tests']`, whose vitest lane collects `**/*.test.ts` only (`integration-tests/vitest.config.ts:21`), so a future PR touching only the exporter and its `.mjs` test is told to run a lane that cannot collect the file - the same silent drop `HELPER_TESTS` exists to prevent. Add a narrower rule for `integration-tests/concurrent-runner/**` recommending `helper-tests`. Matching rules merge rather than first-match, so the lane list for that directory becomes the union and every other `integration-tests/` path is untouched. The pinned `expectedManifest` in `manifest-repository-context.committed.test.ts` moves with it, which is what that pin is for. Verified through the real provider (tsx importing `packages/cli/src/commands/review/lib/manifest-repository-context.ts`) because the packages/cli vitest globalSetup gate refuses to run on this unbuilt worktree: concurrent-runner .mjs -> ["helper-tests","integration-tests"] concurrent-runner .js -> ["helper-tests","integration-tests"] chat-transcript-document.test.ts -> ["integration-tests"] (unchanged) all 10 rules co-matching -> non-null, 80 relatedPaths, and every `paths` probe matched some rule Also checked the committed manifest deep-equals the test's pinned literal (10 rules on both sides), and that dropping the new rule breaks that equality, so the pin is not tautological. `npx prettier --check` and `npx eslint` clean on both files. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtpfg5ewmp * fix(deps): sync pnpm lockfile for Playwright pin --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
078b924989
|
refactor(ci): extract release workflow scripts (#11165)
* refactor(ci): extract release workflow scripts
* test(ci): pin release-note labeling to the extracted step script
release.yml no longer inlines the auto-labeling body: the publish step now
just dispatches to run-release-step.sh, which owns the git rev-list
enumeration and the classify-release-notes.mjs call. The wiring test kept
asserting those four strings against the step's run block, so it failed on
the dispatcher path alone.
Read run-release-step.sh and assert the labeling body there, and assert the
dispatch from release.yml. The workflow-level guarantees (step name,
continue-on-error, GITHUB_TOKEN env, issues/pull-requests write permissions)
and the .github/release.yml exclusion entry are unchanged.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-conflict/jmtpmlcodmz
* test(ci): repoint release assertions at the extracted step script
The extraction moved the GitHub Release notes, standalone archive, and
release-failure autofix label logic out of .github/workflows/release.yml
into .github/scripts/run-release-step.sh, but three consumer suites still
pinned the moved text in the YAML, so `npm run test:scripts` was red and
the release workflow's fail-closed `quality` gate would refuse to publish.
Read the step script in ai-release-notes-workflow, install-script, and
qwen-autofix-workflow and assert the moved strings there with their new
lowercase locals (`notes_args`/`notes_file`), keeping on release.yml only
what genuinely stayed in it. Restore the "Safe to auto-apply approval"
rationale above both AUTOFIX_APPROVED_LABEL writes in the notify-failure
arm; it is the only recorded justification for the workflow granting
itself autonomous autofix approval.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtq0vr74ni
* test(ci): derive the ECS wipe subject set from the runner, not the wipe
Selecting checkout jobs by "has a Restore workspace ownership step" made
the subject set a function of the property under test, so a job added to
the ecs-qwen-hk4-host pool with an actions/checkout and no wipe was
filtered out before any assertion ran. Key the exemption on the pool
marker instead, and pin that every pool-routed job carries the wipe
whether or not it checks out.
The hosted label cannot be the discriminator: every pool-routed runs-on
expression names 'ubuntu-latest' as its fallback branch.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtq0vr74ni
* test(ci): re-scope the release-creation token pin to its own step
The workflow-wide toContain was satisfied by the second CI_BOT_PAT
occurrence on "Trigger ECS runner qwen update", so flipping the token on
"Create GitHub Release and Tag" to github.token left every lane green.
Restore the step scoping and the deleted rationale comment in release.yml.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtqfvvmto2
* fix(ci): fail the release step runner closed on pipeline legs
run-release-step.sh set bare `set -e` while both sibling runners set
pipefail. In notify-failure a connection-level gh failure inside
`gh issue list ... | jq -c ...` leaves jq exiting 0 on empty input, so the
empty result took the create-a-new-issue branch and filed a duplicate
autofix/approved release-failure issue past all three reuse guards.
Pin the flag next to the existing sibling pin and add a behavioural row for
the notify-failure arm with an unreachable gh.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtqfvvmto2
* test(ci): relocate the release timeout value-format contract
This PR condensed the nine-line release.yml lane comment to "values are
minutes" and relocated the format half nowhere: grep for the deleted claims
(expression error / free text / truthy / leading number) found no copy under
.github/, scripts/tests/ or docs/. Put the contract beside the assertions that
already pin the three tunable lanes' expressions, since release.yml is
size-pinned twice.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtqfvvmto2
* docs(ci): scope the workflow_sha claim to the extracted helpers
The design doc said the trusted checkout "prevents an operator-selected
release ref from supplying code that receives release credentials". Two paths
at HEAD contradict that reading: resolve-version executes the ref's
scripts/get-release-version.js under the job token (run-release-step.sh:84),
and push-release-branch commits with core.hooksPath .husky (:116, :130) in a
step whose env carries CI_BOT_PAT (release.yml:659). Both predate this
extraction, so name them as residual paths rather than as prevented ones.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtqfvvmto2
* fix(ci): format the release-version test to satisfy the Prettier gate
`Lint & Static` failed at the `Run Prettier` step: a continuation line in
the `assert-unreleased` format-gate test carried four extra spaces.
* fix(ci): exclude nested .git dirs from the docker build context
The extraction added a `.release-workflow/` checkout to `integration_docker`
between the sandbox build's reset and the build itself, and `.dockerignore`
listed `.git` only in its root-anchored form. A depth-1 sparse checkout of
the pinned ref is 1.1M of worktree over 51M of git objects, so every
uncached sandbox image baked those objects into a discarded builder layer on
the shared pool this job is routed to. `node_modules` and `dist` already
carry both forms.
* fix(release): close four gaps the script extraction left behind
R1-15: the push-time guard returned exit 2 for a malformed version, and
run-release-step.sh treats 2 as a transient probe failure — so a refusal
that can never succeed was retried three times over ~45s and logged as a
connectivity problem. Malformed versions now exit 4, which the retry loop
breaks on immediately and which is not the exit-3 version_refusal marker.
R1-16: the extraction moved the guard behind `.release-workflow/` so the
decision comes from the workflow-pinned SHA, but left a working
`--assert-unreleased` dispatch in the ref-supplied get-release-version.js.
A future step using that spelling would load the selected ref's guard.
Removed it, and re-pointed its five tests at the real entry point.
R1-13: assert-release-version.mjs ships alone under release.yml's
sparse-checkout set, so it cannot import release-helpers.js — the
duplicated isExpectedMissingGitHubRelease is deliberate. Documented that
at both sites and pinned the two bodies identical, so widening only the
guard's copy (which would read a rate-limited probe as "release absent")
goes red.
R1-6: restored the notify-failure arm's rationale, matching the condensed
comments the other five arms kept: the exact-title re-check that stops a
v0.18.1 failure reusing a v0.18.10 issue, the bot-author preference, and
the re-query that keeps autofix off a maintainer-owned issue.
* fix(release): re-pin the trusted runner before every step that runs it
R1-10: `Build Bundle and Prepare Package`, `Build Standalone Archives` and
`Verify Standalone Archives` executed `.release-workflow/.../run-release-step.sh`
from a tree that selected-ref npm code had already run over — `build-package`
for the first two, and `npm publish`'s own lifecycle scripts for the third,
inside the job that holds `id-token: 'write'`. At the merge base these were
inline `run:` bodies sourced from the workflow file, immune to anything
written into the working tree, so the exposure is introduced by the
extraction. Each now gets the reset-and-re-checkout pair the design doc
already promises for credential-bearing steps.
The guarding test hand-listed six protected step names, so it could not fail
for the three it omitted. It now sweeps every step whose `run` mentions
`.release-workflow/` (17 today) and separately pins that each re-checkout is
preceded by a reset.
R1-5: nothing tied the publish allowlist to the guard's `PUBLISHED_PACKAGES`
except a comment pointing at publish steps this PR deleted from release.yml.
A channel added to the loop but not the array would ship unprobed, so a retry
of a partial release reports "unreleased" and force-pushes over the tip the
shipped package anchors to. The two sets are now pinned against each other,
and the stale comment points at the real pin.
The three inserted step pairs grow release.yml by 294 bytes (well inside the
4096-byte allowance); the ratchet baseline and the PR description move with
it rather than letting the recorded number drift.
* test(release): pin the flags, arms and modes the extraction left unwitnessed
R1-19: the arm's only executable probe stubbed npm with a script that
recorded `$PWD` and discarded every argument, so deleting `--tag=${NPM_TAG}`
stayed green while `npm publish` would fall back to the `latest` dist-tag —
handing the 21:00 UTC nightly to every end-user install and to the ECS fleet
updater. The stub now records the argv and each publish is checked for
`--access public` and the dist-tag.
R2-3: `resolve-version` was the one arm no test executed or text-pinned;
flipping `--type=preview` to `--type=stable` survived all 2233 scripts tests,
while at runtime the stable path ignores `preview_version_override` and would
publish an operator's manual preview as a stable release. Two execution tests
now cover the preview mapping and the malformed-version rejection; both were
run against the real arm before pushing.
R1-18: release.yml invokes these scripts by bare path, so the executable bit
is load-bearing, but every test invokes them as `bash <script>`, which
ignores the mode — and the workflow triggers only on schedule/dispatch, so no
pull-request lane runs the bare-path form. A mode-normalizing commit would
keep the suite green and kill `set-flags` with exit 126. The recorded git
mode is now pinned for all three scripts.
* test(release): pin the trusted prefix, the fork gate and the real version formats
R1-3: every arm pin elsewhere is a substring match that a bare
`.github/scripts/run-release-step.sh` invocation would satisfy just as well,
so nothing pinned the mechanism the extraction exists for. All 17 step
invocations are now required to carry the `.release-workflow/` prefix and
forbidden from naming the release-ref copy.
R1-9: `PUBLISH_AUDIO_CAPTURE` replaced a deleted step-level
`if: github.repository == 'QwenLM/qwen-code'` gate and, unlike its
`PUBLISH_EXTERNAL_CONTEXT_MEM0` sibling, had neither its gate expression nor
its true branch pinned — a fork running this workflow would publish
@qwen-code/audio-capture. Both halves are pinned now.
R1-14: the format gate is the guard's only genuinely new behaviour and its
accept path was exercised solely with '1.2.3', while the two scheduled
releases produce nightly and preview strings. Both formats are now asserted,
so tightening the pattern fails here rather than at 21:00 UTC.
* fix(release): keep failure reporting alive when the trusted runner is not
R1-11: `notify_failure` gained a checkout of `github.workflow_sha` as its
first step, and its whole body now lives in a shell script. At base the job
had no checkout at all and the notifier was inline, so the job whose only
purpose is reporting every other job's failure acquired two ways to fail
silently: a degraded git backend takes out the checkout, and a syntax error
or dropped exec bit in run-release-step.sh takes out every arm including
notify-failure. In both cases `Create Issue on Failure` carried the default
`if: success()` and was skipped — no issue, no autofix label, no dispatch,
and the scheduled autofix fallback scans issues, so nothing recovered it.
Rather than moving 105 lines of notifier back into the workflow — which the
orchestration guard in this PR forbids at 12 run-lines per step — the primary
path keeps the extraction and stops being load-bearing: it runs under
`always()`, absorbs its own failure, and a five-line inline fallback files a
plain issue whenever it did not succeed. The fallback depends on neither the
checkout nor the script, does no reuse, labelling or dispatch, and accepts a
duplicate issue as the cost of never going silent.
The workflow's line ceiling moves 800 -> 830 for those lines, with the reason
recorded at the assertion; the per-step run cap that keeps logic out of the
YAML is unchanged and still holds. Ratchet baseline and the PR description
move with the file.
* style(release): apply the pinned Prettier to the fallback notifier
Prettier 3.6.1 normalizes the quoting on the new `if:` and drops a
trailing space; the ratchet baseline follows the two-byte change.
* docs(release): name exit 4 where the guard's exit codes are described
Adding the malformed-version code left two descriptions behind: the retry
loop had no statement of the contract at all, and the test comment beside it
still said exit 0 and exit 3 were the only decisive outcomes.
* fix(release): avoid duplicate fallback failure issues
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: yiliang114 <jinjing.zzj@gmail.com>
|
||
|
|
0d1e0fbfa6
|
feat(core): configure model reasoning capabilities (#10999)
* feat(core): configure model reasoning capabilities * fix(core): validate configured reasoning metadata * fix(core): preserve configured model casing * fix(core): retain case-variant model entries * fix(core): decide reasoning-capability validity in one shared parser (#10999) The request pipeline read ModelConfig.capabilities.reasoning straight off the resolved model while every reasoning control validated it first, so a capability the pickers refused still reshaped the wire. With disableField missing -- the capability's only member that has no fallback -- a tier the UI never offered was dropped instead of shipping, and an unparsable canDisable: false flipped thinkingMandatory and swallowed an explicit thinking-off. One parser in core now decides validity for both sides, and an incomplete entry leaves the model on its pre-capability behavior everywhere. That removes the CLI's duplicate validator rather than adding a second one. A reasoning object the user put in samplingParams also went through the capability mapping, breaking the verbatim contract clampConfiguredReasoningEffort already keeps: the tier was deleted before the provider hook could translate it. It now passes through untouched. Both effort pickers mapped a configured tier the resolved model does not expose onto index 0, which read as "this tier is current" and let a bare Enter persist it over the stored global setting; they now say the tier is unavailable instead. /effort <tier> on a model with no tiers reports that rather than an empty "Choose one of:" list. Every new guard has a mutation witness: removing the pipeline parser, the samplingParams guard, either disableField branch, the canDisable clause, the dialog out-of-range handling, the efforts prop, or the hoisted zero-tier check each reddens the test that pins it. The two closed partial mocks of the core package now supply the real parser so the acpAgent reasoning paths stay exercised. * fix(cli): route the turn-index handler through the runtime-root choke point (#10999) * fix(core,cli): reject malformed reasoning capabilities (#10999) The shared parser cast three capability members through unchecked while every consumer reads them strictly, so a mistyped value in an unvalidated `modelProviders` settings entry silently changed the declaration instead of failing it. A truthy non-boolean `toggleOnly` collapsed a declared ladder to toggle-only: the pickers refused every tier while `disableField` still reshaped the wire. `efforts: []` passed `[].every()` and emitted a zero-tier capability that suppressed the manifest fallback and stripped the effort from the request. A non-`false` `canDisable` degenerated to absent, so a route declaring that disabling is forbidden still shipped the disable shape that declaration exists to prevent. All three are now rejected rather than coerced, along with a repeated tier, which duplicated picker rows, ACP options and React keys. Well-formed input keeps the identity contract. Both effort pickers forced the cursor onto the first tier when the stored global `model.reasoningEffort` is not one the active model exposes, and confirming without moving persisted that tier over the stored value -- the rewrite measured live as F2. A forced cursor now cancels in the ink dialog and closes without writing in the OpenTUI one; navigating first still selects, and the unset case is unchanged. The OpenTUI footer discloses the unset case too, matching ink. Four output-style dialog tests drove keys before the effect deriving the cursor from the async catalog had run, so that derivation could overwrite the navigation and pick the wrong row; they now wait for it. Every new guard condition has a mutation witness: removing the toggleOnly, canDisable, empty-efforts or uniqueness check, the ink cursor-moved or stored-tier condition, or the OpenTUI forced-cursor condition each reddens the test that pins it, and acceptance of a well-formed `canDisable: false` or `toggleOnly: false` is pinned as well. Not changed here: the pipeline still drops a carried-over tier the capability does not list instead of clamping it onto the ladder. The maintainer's real-stack A/B records refusing an unsupported tier as correct and files the carried-over case as a picker problem, and the author's triage answer names the drop as the requested migration contract, so the clamp stays a maintainer call and its thread stays open. The turn-index per-request settings test is deferred: this round's merge of main absorbed the identical handler fix (#11036), leaving only the roster docstring here. * fix(core,cli): address round-6 review findings on model reasoning config (#10999) - Gate the capability disableField emission on non-qwen-family wire models: the qwen-family branch above already emitted the route-correct disable shape, and re-emitting the declared field after it resurrected the deleted top-level enable_thinking (or paired it with reasoning_effort: 'none' on tiered DashScope). - Key capability resolution on the registry base URL with an unscoped fallback at both the picker and the wire, so an env/flag/settings endpoint override no longer hides a declared capability from one surface while the other still advertises it. - Derive the effort dialog's explicit-choice signal from the user's own input (digit key, navigation key, or click via a new onSelectIntent) instead of highlight state, which pointer hover also moves; confirm single-row lists instead of cancelling them; latch the OpenTUI twin's forced-cursor guard on movement rather than cursor position. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * revert: remove review-driven scope expansion * fix(cli): keep runtime snapshot request overrides on ACP auth refresh (#10999) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * Revert "fix(cli): keep runtime snapshot request overrides on ACP auth refresh (#10999)" This reverts commit |
||
|
|
bbc8ea6488
|
feat(goal): pause a Goal after three autonomous turns that make no progress (#11239)
* feat(goal): pause a Goal after three autonomous turns that make no progress An autonomous Goal had two bounds and neither noticed one going in circles. The token budget is the only thing `queueContinuation` stops for, 30M tokens by default, and the checkpoint stall streak measures the opposite problem: it fires when a Goal produces too much evidence to catalogue, and a Goal producing none never advances it at all. So a model that answers each turn with a paragraph of status and calls no tools continued until the budget ran out, with nothing on the record that could ever end the loop. A Goal now pauses after three consecutive autonomous turns that recorded no evidence-bearing tool result and proposed no terminal state. `get_goal` and `update_goal` results do not count -- they carry `provenance: 'goal_runtime'`, and a turn that only re-reads its own state is the clearest form of the idling in question. Only the runtime's own continuations are measured: a turn carrying the user's text is the user steering and restarts the streak, and so does a user turn reserved while the third quiet turn was still running. The wind-down hand-off is exempt from both halves. The streak lives on the record as `noProgressTurns`, exactly like `checkpointStalls`, so a restart cannot launder it, and edit, replace, and every resume clear it -- resuming is the user asking for another run at the objective, not for its last turn. The count comes from a new optional `takeGoalTurnToolResults` on the renamed `GoalTurnLedger`, fed by the recorder where tool results are already stamped with the Goal permit. Optional is load-bearing: a ledger that cannot answer switches the bound off rather than reporting every turn as idle, and the bound reads the count measured on this turn rather than the one on the record for the same reason. The stop is a pause, not `blocked` or `usage_limited`: nothing was spent and there is no evidence to cite, the Goal simply stopped producing. Following the pause-reasons design, no new `GoalStateCause` is introduced and no host changes -- every surface already renders a paused Goal's `lastReason`. * fix(goal): read an uncountable tool-result answer as unmeasured A ledger that returns something other than a finite count was read as zero, which spends one of the three turns on a measurement that never happened. It now says the same thing as a ledger that throws: the bound is off for that turn. Also pin that the turn which spends the streak takes no checkpoint -- the guard was in the code with nothing holding it there -- and point the design doc's verification list at the pull request's evidence rather than at an ignored path. * fix(goal): let the no-progress bound yield to a waiting turn, a spent budget, and a stalled checkpoint Three of the bound's neighbours outrank it, and the pause write opened a window where a fourth was lost. A user turn reserved while the pause record was being appended was discarded: `beginTurn` is synchronous and does not queue, the guard read the reservation once before the await, and the later `queuedTurnKey` assignment erased it. The guard now re-reads the reservation after the write and declines the pause when one appeared, the way `stopForSpentBudget` re-validates after its own write. The journal may then hold a `pause` record the runtime never adopted; that is the shape the budget stop already leaves, and the conservative side to land on -- a restart recovers a paused Goal with its reason, and resume is the whole remedy. The bound also stands down on the turn where a better-fitting limit coincides with it. A spent token budget reaches the continuation gate, which grants the wind-down hand-off and stops as `usage_limited`/`token_budget` instead of an idle pause with no `limitKind`. A Goal carrying a checkpoint stall streak lets its checkpoint run, so the stall breaker stops it with the evidence-catalog reason instead of a pause whose remedy would resume it into the same overflowing window. The paused snapshot is now built by the same `settledSnapshot` helper as the usage-limited one, so the settled shape lives in one place. Tests: a reservation landing inside the pause write is served; a restored streak at the limit is not spent by a turn the ledger cannot measure; the stall breaker wins three quiet overflowing turns; the budget wins the crossing turn. Each goes red under the corresponding mutation. * fix(goal): keep the no-progress pause reason host-neutral and accurate The reason is runtime-emitted and headless-reachable, so it must not point at a slash command a process that has already exited cannot run; and it said "no tool results" about a Goal whose transcript shows three turns of `get_goal`/`update_goal` results, which are recorded but do not count. It now says "nothing to judge", offers resume, and offers edit only together with resume -- editing a paused Goal leaves it paused. The user doc says the same and names the bookkeeping reads that do not count. The constant joins the headless-register test, which now also rejects any `/goal ` mention, and the shared-constant validation loop. * fix(acp-bridge): replay only a typed pause as the user typing it The replay projected every `pause` record as the user typing `/goal pause`. The no-progress bound is the first `pause` the runtime writes with nobody at the keyboard, so a replayed session attributed the autonomous stop to the person who was away. A `pause` now replays as a user message only when its reason says the user typed it (`GOAL_PAUSE_REASON_COMMAND`, exported on the goal wire) or when the record predates pause reasons; every other pause replays as the paused card alone, which carries the reason. `resume` and `clear` are unchanged. * fix(goal): preserve steering checkpoints and pause feedback --------- Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com> |
||
|
|
73af280034
|
feat(channels): add shared multiline instructions field to channel management (#11082)
* feat(channels): add shared multiline instructions field to channel management
Every manageable built-in channel type now exposes a shared optional
instructions descriptor (kind string, multiline), and the Web Shell
channel editor renders multiline string fields as a textarea. The
runtime already consumes config.instructions per channel session;
this closes the management gap so the value can be set and reviewed
through the editor and management API instead of hand-editing
settings.json.
* fix(channels): correct shared instructions copy and pin multiline descriptor
The two tests this PR added pinned field key names only, so deleting
`multiline: true` from the shared descriptor left the CLI suite green
while `GET .../channel-types` served `instructions` as a plain string
field and the Web Shell rendered it through the single-line fallback.
Assert `kind` and `multiline` next to the key-list check.
The shared description also promised *extra* guidance for every
manageable channel, but dingtalk substitutes its own default block when
config.instructions is set (DingtalkAdapter.ts:908) instead of composing
like dws and github do. Reword to channel-neutral copy and pin the
qualifier so the additive promise cannot return silently.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmto8kxx1l2
* fix(web-shell): group channel instructions with conversation management
`instructions` joined the shared label map but neither section key set,
so the catch-all credentials filter swept it and the guidance textarea
rendered under "Credentials", after Client ID / Client Secret. It is a
clear-text field that gets none of the secret-kind handling, so add it
to SHARED_SESSION_FIELD_KEYS and let it render with the other shared
session controls. Also align the EN/ZH description with the corrected,
channel-neutral copy: dingtalk replaces its own default guidance when
this value is set.
Cover the multiline render branch, the only consumer of field.multiline
in the repo and the whole user-visible half of this change: a multiline
string field renders a TEXTAREA while a plain string field still renders
an INPUT, the control sits in the Conversation management section, and a
two-line value reaches onSave with the newline intact.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmto8kxx1l2
* test(channels): pin the shared instructions descriptor on every manageable built-in
The registry skips the shared `instructions` injection for any channel that
declares its own key, so the builtins suite only covered the synthetic
test-local plugin. Loop the manageable built-ins from the real catalog and
assert each serves exactly one `instructions` field carrying `kind: 'string'`,
`multiline: true` and the neutral copy. Without the render hint the editor
falls back to a single-line input, which flattens an operator's stored
multi-line guidance on the first edit.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtp6vibomd
* test(web-shell): pin the stored multiline instructions value in the editor dialog
All three dialog cases added for the multiline control rendered in create
mode, so the Textarea's display wiring was never exercised with a stored
value: replacing `value={String(value ?? '')}` with `value={''}` left the
whole suite green while an operator editing a configured channel saw an empty
Instructions box whose first keystroke replaced the stored guidance block.
Add an edit-mode case asserting the draft value reaches the textarea and
survives the save round trip. The fixture carries no outer whitespace because
createChannelEditorDraft loads untrimmed while assignField trims on save.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtp6vibomd
* i18n(web-shell): localize the channel noun in the zh instructions description
The zh value for channels.editor.field.shared.instructions.description was the
only Han-valued entry in the whole ZH map that still carried the raw English
noun, on a screen that renders it as the established spelling used by
channels.title (i18n.tsx:6484) and sidebar.channels (i18n.tsx:4902). Use that
spelling for both occurrences, and keep it distinct from
daemon.runtime.channel (i18n.tsx:4394), which names a different concept.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtp6vibomd
* fix(channels): scope multiline to string descriptors and pin the rendered copy
Two review findings on the shared multiline instructions field.
1. `multiline` was declared on `ChannelConfigFieldDescriptorBase`, which made it
type-legal on every descriptor kind while its only reader honours it on
`kind === 'string'`. Mirror the existing `envResolvable` shape instead: the
attribute moves onto the string-carrying value descriptor, becomes
`multiline?: never` on the plain-value, enum, number and object descriptors
and on nested properties (which never reach a control at all - `renderField`
returns null for `kind === 'object'`), identically in `channels/base` and in
the SDK daemon mirror, with the wire-shape allowlist admitting the key only
inside the existing string/secret guard.
2. The built-in-wide description pin in `channel-registry.test.ts` guarded a
registry literal the Web Shell never renders: `fieldDescription` resolves
`${labelKey}.description` and returns the i18n value whenever that key
translates. Scope the copy pin to channels that do not declare their own
`instructions` field (the registry skip branch), keep the two render
invariants unconditional, and move the copy guarantee onto the surface an
operator actually reads - one EN and one zh-CN assertion on the rendered
textarea description, each matching text present only in that locale so the
`messages[key] ?? EN[key] ?? key` fallback cannot mask a deleted key.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtpfg5ewmp
* docs(daemon): document the multiline channel descriptor modifier
This PR puts `multiline` on the daemon descriptor wire contract, but the
descriptor paragraph in qwen-serve-protocol.md only documented its sibling
client-interpreted modifiers `properties` and `exclusiveMinimum`. A third-party
daemon client implementing the channel editor from that page would render every
`kind: 'string'` field as a single-line input, and HTML value normalization
strips CR/LF on both parse and write-back. Since writes replace each field's
stored value wholesale, the first save of an unrelated field would persist the
flattened value.
Document `multiline` beside its siblings: it applies to string and secret
descriptors (both carry it in ChannelConfigValueFieldDescriptor), and the
descriptor types restrict it to top-level fields. Phrased as a type/contract
restriction rather than daemon enforcement, because assertManagementField
rejects misplaced envResolvable and exclusiveMinimum but has no multiline
branch. Also extends the existing preservation rule to cover a client that
renders such a field in a single-line control.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtpq5ybkn4
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
||
|
|
965f498fd6
|
feat(cli): reap owned worktrees when daemon sessions are deleted (#11309)
* feat(cli): reap owned worktrees when daemon sessions are deleted POST /sessions/delete removed the session record and sidecar but never the owned checkout, marker, git registration, or branch, so deletes of worktree-owning sessions leaked them permanently. deleteDaemonSessions now classifies ownership before the record deletion (strict sidecar + marker + containment + cross-sidecar sharing scan, executed under the worktree ownership lock moved to a shared path-keyed module) and, after a confirmed removal, safely deletes the checkout and branch — never forcing the branch delete, preserving dirty checkouts, superseded predecessors, tombstone-shared checkouts, and anything doubtful with a named log line. Refs #11024 * feat(cli): count never-committed files as work in the orphan cleanup gate The cleanliness gate now runs a full git status --porcelain (untracked files included, marker file exempted) so an agent-written draft that was never committed preserves the checkout instead of being silently destroyed. Also covers the invalid-marker branch and fixes the design doc's logger reference. * fix(cli): pin the destructive worktree cleanup to what the guards verified - assert the removal target (originalCwd + slug) realpaths to the checkout every guard inspected, and consume the locked sidecar - pin git status flags and scrub the environment in checkoutHasWork, exempting only disposable build output from the ignored tier - refuse to fold the record on a session_closing refusal from a bridge-internal auto-close - re-assert the workspace generation guard before the destructive step - re-observe the checkout before logging preservation on removal failure, naming partial deletion when the shell is empty - reject non-absolute or foreign sidecar bases and thread the runtime workspace cwd into the containment check - keep no-sidecar deletes on the synchronous prefix so batch scheduling stays deterministic (gate-race flake) |
||
|
|
3ecfaffdf8
|
perf(dev): add pnpm worktree bootstrap foundation (#10449)
* perf(dev): add pnpm worktree bootstrap foundation Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(dev): harden worktree bootstrap process handling Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * perf(dev): prefer cache-only worktree installs Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(dev): preserve cached install cancellation Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(build): harden pnpm worktree bootstrap Validate pnpm lock updates in releases and exercise real installs and builds across supported hosts. Preserve npm release compatibility and keep dependency-only setup from rewriting npm-layout notices. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * chore: record merge attribution Record the required attribution for the Stage 1 merge without rewriting published history. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(dev): keep worktree bootstrap clean Use a bootstrap-scoped notice guard because nested npm lifecycle commands replace npm_lifecycle_event. This preserves explicit notice generation while preventing dependency setup from rewriting tracked output. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * ci: track pnpm smoke workflow size Register the new workflow in the repository size ratchet as required by the main CI gate. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): drop stale importers from the new pnpm lockfile The lockfile was generated before the WebShell cutover (#9811) removed @qwen-code/webui from web-shell and the tailwind tooling plus @qwen-code/webui from vscode-ide-companion, so frozen-lockfile installs fail on all three smoke platforms with ERR_PNPM_OUTDATED_LOCKFILE. Drop the five stale importer entries so the lockfile matches the current package.json manifests; verified with pnpm 11.24.0 install --frozen-lockfile --lockfile-only. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): refresh pnpm lockfile after merging main The merge of origin/main added remend@^1.3.1 to packages/cli/package.json without updating pnpm-lock.yaml, breaking the pnpm Worktree Smoke workflow frozen-lockfile install. Regenerated with pnpm install. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-conflict/jmtid3rse9h * fix(scripts): resolve the path variable case-insensitively on Windows A spread of process.env is an ordinary object, and native Windows shells expose the path variable as `Path`, so `env.PATH` was undefined there and findOnPath never located corepack — the offline-first Corepack bootstrap silently degraded to npx on exactly the hosts it exists for. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(scripts): pin the worktree bootstrap guards against mutation - Split the fallback log on /\r?\n/ like the sibling cmd.exe mocks, so the assertion holds on the Windows lane's CRLF output. - Add a win32 variant bootstrapping with the native `Path` casing to pin the case-insensitive lookup. - Assert the smoke workflow's fail-fast flag, the install-before-clean step order, and the no-build guard via a substring on the raw job text. - Extend the pnpmfile rewrite fixture to devDependencies and optionalDependencies, which the committed lockfile already uses. - Assert the notice-skip guard by effect (writeFile never called) and add the flag-absent companion test with I/O stubbed. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * chore(deps): sync pnpm-lock.yaml with qwen-live ACP backend deps Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-conflict/jmtjyz3tdco * fix(dev): stop patch-package from corrupting the pnpm store The root postinstall runs patch-package, which rewrites files under node_modules in place. With pnpm's default import method those files are hard links into the content-addressable store, so the patch rewrites the store entry too and its contents stop matching the sha512 it is filed under. Every subsequent fresh worktree then fails its --offline stage with ERR_PNPM_NO_OFFLINE_TARBALL, silently falls back to the registry (exit 0), and the fallback's postinstall corrupts the store again, so the offline path never hits. The advertised offline timing was only reachable on a same-tree reinstall, which needs no relinking. Set packageImportMethod to clone-or-copy: a copy-on-write clone where the filesystem supports it, a plain copy elsewhere. Either way patch-package edits only the worktree's own copy and the store entry stays intact, so later worktrees resolve entirely from the store. Verified on an isolated fixture store: under the default method the node_modules file and its store entry share an inode, and an in-place edit moves the store file's sha512 off its address, after which a fresh offline install fails with ERR_PNPM_NO_OFFLINE_TARBALL; under clone-or-copy the same edit leaves the store hash unchanged and the fresh offline install reports reused 1, downloaded 0. Also add pnpm-lock.yaml to .prettierignore. prettier reflows it into a shape pnpm does not emit, so `npm run format` and `pnpm install` fight over the file; .yamllint.yml already ignores it for the same reason. Both facts are pinned by tests in scripts/tests/package-scripts.test.js. Claude-Session: https://claude.ai/code/session_012797rgiteWJxLT9TLkKq8G * fix(ci): refresh pnpm-lock.yaml for qwen-live prompts deps Main added prompts/@types/prompts (and the ACP sdk) to packages/qwen-live/package.json after this branch generated its lockfile, so the pnpm Worktree Smoke frozen-lockfile install failed with ERR_PNPM_OUTDATED_LOCKFILE. Regenerated with pnpm 11.24.0 (--lockfile-only); frozen-lockfile and supply-chain policy checks pass. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-conflict/jmtlc9sozfg * fix(pnpm): close review findings on worktree bootstrap - Declare @qwen-code/qwen-code-core in vscode-ide-companion so the pnpm linker materializes it; regenerate both lockfiles and assert the link in the smoke workflow (R5-1) - Pin @types/node 20.19.1 in packages/core so the pnpm layout compiles against the same types npm hoists (D6-1) - Accept and pin the corepack +sha512 integrity suffix on packageManager; strip it for the npx fallback spec (D6-2) - Cover every workspace member in the pnpmfile rewrite set, mirror the npm channel list in pnpm-workspace.yaml, and cross-check both in tests - Run setup-worktree.js installs with cwd pinned to the checkout, gate the smoke clean-check on git status --porcelain, stop post-merge runs from cancelling each other, and extend check-lockfile.js to pnpm-lock.yaml * merge(main): realign pnpm workspace set and lockfiles Main removed the webui and cua-driver packages; drop both from the pnpm rewrite set and regenerate the pnpm lockfile against the merged manifests (picks up playwright, react-markdown, and the other main-side dependency changes) so the frozen bootstrap matches what the PR merge ref will run. * style(ci): quote smoke workflow scalars per yamllint * fix(dev): require Corepack for pnpm bootstrap * fix(dev): close pnpm bootstrap review blockers --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: yiliang114 <jinjing.zzj@gmail.com> |
||
|
|
663e55bd29
|
feat(web-shell): refine file cards and configure drop destination (#11298)
* feat(web-shell): customize artifact icons * fix(web-shell): preserve artifact icon fallbacks * fix(web-shell): improve artifact fallbacks * feat(web-shell): refine file cards and configure drop destination * fix(web-shell): address file card and drop review feedback --------- Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com> |
||
|
|
96b14f2311
|
feat(web-shell): add a context usage tab to the right sidebar (#11177)
* feat(web-shell): add a context usage tab to the right sidebar Surface current context-window composition beside the existing token consumption tab. The new opt-in header action opens a session-owned context_usage artifact tab that reuses the /context renderer in a compact sidebar layout, with manual refresh and owner-bound restoration. The data path already exists (getContextUsage with detail), so this adds no endpoint or core change. Tabs deduplicate, persist, restore lazily against their saved session, and close with their pane or session, mirroring the token tab lifecycle. Transcript /context behavior is unchanged. * fix(web-shell): address context tab review round 1 - Share one transient-read predicate between the two usage panels so both swallow disconnect, transport-close and fetch-failed identically. - Keep the last good context reading during and after a failed refresh; only owner changes clear it. - Dedupe the mount collection across StrictMode replays with an owner-keyed in-flight request instead of a closure-local flag. - Reclaim pane-bound usage tabs at restore completion; the mount-time pane cleanup runs before restoration lands, so they survived reloads into a non-split view. - Let the compact panel opt out of the 30-char detail-name cap, render the progress meter as proportional blocks, uncapped meta row and indented sub-rows, and align the zh-CN wording with the established term. - Pin the gaps the review measured: transcript-insertion negatives, pane action owner binding, primary-session restore arm, standalone opt-in, no-actions render, and the compact/transcript truncation contract. * fix(web-shell): address context tab review round 2 - Reclaim pane-bound usage tabs from an effect gated on a new splitViewSettled flag instead of at restore-commit time: mainView and splitSessionIds are still their useState initials when the restore continuation runs, so deciding there dropped legitimately pane-bound tabs and wrote the drop back to storage. - Rethrow transient getContextUsage failures without a notice, mirroring getStats: the context panel re-collects on every activation, so the notice channel stacked identical errors while a session was down. - Emit the compact meter segments in legend order (used, free, buffer) and pin widths and threshold colors in tests. - Pin the alert-over-retained-reading precedence, the remaining reclaim filter quadrants, and the reachable pane-bound restore shape; type the pane context fixture and build the pane actions mock as an override of the main one so it satisfies the declared contract. * fix(web-shell): address context tab review round 3 - Settle the split latch at every bootstrap exit, including superseded classifications and the non-workspace bail, and gate the non-split close twin on it so a live pane tab is never dropped mid-decision. - Re-apply the pane-bound predicate at restore commit once the latch is already true, so a stale tab cannot be selected active and mount a foreign collection; defer to the settled sweep while the decision is pending, and re-run the sweep when the restored tab list lands. - Make getContextUsage silence opt-in (silent) so the auto-recollecting panel stops stacking notices while user-initiated callers keep the attributed notice and suppressed duplicate toast. - Pin the gaps the round measured: uncontrolled split bootstrap paths, superseded-classification settle, language-change survival, closed-panel restore, serialization of closeWithPane, binding replacement on re-open, render assertions for restored payloads, aria-busy during background refetch, clearing of unusable refresh results, transcript glyph math, meter-legend agreement, and the widened transient predicate at call sites. * fix(web-shell): address context tab review round 4 - Re-arm the split latch when a classification actually starts, in both openSplitView and the controlled split effect, so a later decision re-opens the reclaim window instead of being licensed by an earlier settled one. - Close the right panel when the restore-commit reclaim empties the tab list, mirroring closeArtifactPanelTabs, so an emptied panel is not persisted open. - Dedupe silent hard context-usage failures through the existing silentHardFailureNoticeKeys pair, matching getTasks/getWorkflowTasks. - Delete the non-split close twin subsumed by the settled sweep, which also removes its missing artifactPanelRestoring guard. - Pin the re-arm, the do-not-settle branch under overlapping classifications, the emptied-panel close, the deep-link storage purge with a positive control, and the silent hard-failure notice semantics. * fix(web-shell): address context tab review round 5 - Settle the split latch on a terminal capabilities error in the controlled split effect, so a capabilities blip cannot strand the latch unsettled while no classification can start; a merely pending load still defers. - Route silent context-usage hard failures through noticeForSession and gate dedupe-key registration on the session still being live, so the registry stays session-scoped and a stale in-flight failure neither toasts for a session the user left nor suppresses the notice of the session on screen. - Extract resetEmptyArtifactPanel and share it between closeArtifactPanelTabs and the reclaim-emptied commit path so the empty-panel reset cannot drift. - Pin the owning-classification reclaim half of the overlapping-classification test, the capabilities-error settle, the registry teardown clear, and the per-call non-silent notice. --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
f1ed3bc31a
|
feat(external-context): Add daemon memory writes (#11311)
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
npm cache producer / Save npm cache (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 / Real daemon E2E / Java 11 (push) Waiting to run
Add an opt-in workspace-bound writer and complete literal MCP approval previews. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
bc13d167df
|
fix(goal-draft): keep drafts concise and verifiable (#11284)
* fix(goal-draft): keep drafts concise and verifiable * fix(core): align goal-draft gate and budget default with the documented contract --------- Co-authored-by: qqqys <266654365+qqqys@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> |
||
|
|
a4a62258b1
|
feat(web-shell): add continuous history and compact turn navigation (#11208)
* feat(web-shell): add bounded historical transcript viewport * fix(web-shell): preserve historical viewport reading state Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(web-shell): handle pending history entry cancellation Preserve live scrolling when cancelling an in-flight history entry and show retry feedback for a stale boundary without reporting errors for abandoned intents or sessions. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(web-shell): cover historical viewport cache guards Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * feat(web-shell): restore continuous scrolling with compact turn navigation Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(web-shell): sync Phase 3 delivery and verification status Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: jinye.djy <jinye.djy@U-Y37CF7G3-1943.local> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
55926e4617
|
feat(serve): allow concurrent standalone daemons with session fencing (#11207)
* feat(serve): allow concurrent standalone daemons with session fencing * fix(serve): address standalone ownership review feedback Preserve writer-blocked navigation and queued drafts, retain Live admission diagnostics, and cover concurrent daemon and filesystem ownership boundaries. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): handle all writer fences and legacy publishers Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(ci): sync standalone concurrency allowlist assertion Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: jinye <3007091+doudouOUC@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
c526bb80b1
|
feat(channels): Implement named-session worktree reset (Part 4B) (#11015)
* docs(channels): Add named sessions Part 4B design Propose same-worktree conversation replacement for named worktree tasks: a compensatable daemon-side transfer that resets the selected task's conversation while retaining its exact verified worktree. Also absorbs the findings deferred from #10643: missing-marker recovery via reset, deferred-prompt restore attestation, and orphan-reap worktree cleanup bounded by unambiguous ownership and a clean checkout. Refs #10103 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(channels): address self-review of Part 4B design - Idempotent resume short-circuits the marker transfer when the marker already names the replacement, so a post-rename crash retries as a no-op; the resume check also requires the replacement's supersedes back-link. - Manager reset preserves only the creation timestamp; the update and selection timestamps bump as they do on a shared reset. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(channels): revise Part 4B design after review Address the design review on #11015: - Correct the deferred-prompt premise: the under-attesting restore branch serves a genuinely live prompt, so it is removed to fail closed instead of attempting an impossible relocation (C1/S5a). - Surface parked deferred restore prompts in the bridge's active state and consume it in both the coalesced restore (R8-2) and the reset quiescence check (C2); add a reset-pending admission barrier and a pre-flip re-verify so quiescence is enforced, not sampled (M3). - Make the marker transfer a compare-and-swap: owner re-check at the rename commit via atomicWriteFile's assertCanCommit, O_EXCL create for the missing-marker hatch, and worktree-keyed serialization (C3, S1, S2). - Move the orphan-reap worktree cleanup and the R8-1/R8-3/R1-1/F3 fixes out of this part; record every standing Part 4A finding's disposition in a dedicated section (S4, M1, M2). - Add the interrupted-transfer typed signal and message so the one retry-repaired window tells the user to retry. - Credit the review bot's R3-2 alongside the human findings. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(channels): address round-2 review of Part 4B design Scope the R8-2 deferred-prompt visibility fix to the coalesced-restore waiter and getSessionSummary, and state that it must not feed the restore response's hasActivePrompt. Widening it there would push a deferred worktree restore into the active-session check this revision now relies on for the removed branch, failing closed a path that works today. Constrain the marker transfer to atomicWriteFile's rename commit: refuse a marker owned by a foreign uid so the ownership-preserving in-place write is never selected, and record that the sibling temp file keeps the EXDEV unlink-then-create fallback unreachable. Both paths would otherwise break the "never a partial file" guarantee, and a truncated marker reads as invalid, which is never auto-recovered. Use a new synchronous no-follow strict marker reader for the assertCanCommit re-check, since the hook is synchronous and cannot await the async reader; atomicFileWrite.ts stays unchanged. Also record that the reset-pending barrier is deliberately non-durable, and note why the R8-1 rollback block differs from the two neighbouring handlers that preserve on an inconclusive delete. * docs(channels): link Part 4B dispositions to follow-up issue #11024 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(channels): resolve residual worktree session defects from #10643 Four live defects in main, none depending on the Part 4B transfer protocol, fixed with regression tests: - R8-1: the post-spawn generation-check failure block now removes the unowned worktree regardless of the orphan-delete outcome. Unlike the relocation/persistence handlers, no session has entered the checkout at that point, so an inconclusive cleanup cannot strand it. - R8-3: reattach heals a stale persisted-v1 claim when the daemon resumes with no worktree object (legitimate exit), while a response still carrying worktree metadata keeps failing closed. - R1-1: createWorktreeSessionMarkerExclusive unlinks the file it created when the write fails, so the path no longer wedges on EEXIST. - F3: /session new --worktree without a name returns the usage line instead of a misleading name-validation error. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(channels): address round-3 review of Part 4B design - Make the foreign-uid marker refusal conditional on the platform reporting an effective uid (process.geteuid), so Windows — where the ownership-preserving path is unreachable by construction — refuses nothing; both strict marker readers surface the owner uid. - Bound the no-partial-marker claim honestly: the commit-point re-check compares owner and file identity (dev/ino/uid); the residual in-place-rewrite case requires a same-uid writer, which the worktree-keyed lock excludes for daemon writers. - Name the reset-pending barrier's refusal shape (the typed worktree_reset_active failure) and record that Channel callers never observe it because the owner lock serializes the whole reset. - Point the four residual fixes at this PR (code and tests included) and reference follow-up issue #11024. * fix(channels): harden marker cleanup and parser guard per round-4 review - The failed-create cleanup unlinks only while lstat still resolves to the inode this call created, so the identity-changed branch no longer deletes a foreign file swapped in during the write window (verified by a new swap-injection test). - The success-path close() moved past the catch: a close rejection now propagates with the fully written, fsync'd marker intact instead of unlinking it — required by the Part 4B missing-shape commit, whose rollback never removes the worktree. - The /session new guard covers any leading-flag token (parts[0]?.startsWith('-')), not just --worktree; no legal task name starts with '-', so the class is closed without swallowing names. - The reattach heal comment states what the client can actually observe (no worktree object may also mean a cleared in-memory association or a provenance that skips sidecar restore) instead of an exhaustive inference. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(channels): address round-4/5 review of Part 4B design - Critical: the resume path reads the marker before choosing its recovery shape — a committed transfer (marker names S_new) is never rolled back; a busy replacement fails closed with worktree_reset_active and converges on a later retry; the destructive rollback is confined to the pre-commit shape where S_new is provably non-authoritative. Protocol steps 4-5 and the verification plan updated to match. - The no-deletion contract is narrowed in all four places to user-visible/pre-existing state, with rollback permitted to remove only the replacement session's own sidecar and record. - Non-goals inverted: the four residual fixes land in this PR; only the reap cleanup, sibling delete path, and SessionRouter.ts:487 investigation stay out (tracked in #11024). * feat(channels): reset worktree tasks by transferring checkout ownership Implement the Part 4B ownership-transfer protocol so /clear, /new and /reset work on worktree-isolated named tasks: the task keeps its worktree, files and branch, and gets a fresh conversation. Daemon: POST /session/:id/worktree-reset spawns a replacement in the root workspace, relocates it into the verified checkout, links the sidecar pair (supersedes / supersededBy), flips the ownership marker last, then attests persisted-v1 and severs the superseded session. Preconditions fail closed with a bounded typed 409 taxonomy (worktree_reset_unsupported / _active / _invalid_state); a prompt barrier fences the superseded session for the transfer's duration. Crash recovery is marker-driven: pre-commit shapes roll back and complete a fresh transfer in the same request, committed shapes resume idempotently. Restore gains the superseded redirect (worktree_session_superseded + replacementSessionId), the typed missing-marker signal, and the interrupted-transfer classification; the under-attested unlocated-restore branch is removed. Worktree restore/reset serialization is re-keyed from session id to canonical worktree path; its promise-chain release logic is byte-identical to main, so this claims no fix there, and the refused-transfer release test now pins that the lock is released. Core: transferWorktreeSessionMarkerOwner commits through atomicWriteFile with a synchronous strict re-read at the rename commit point, refuses foreign-uid markers where ownership is observable, and recreates a missing marker through the O_EXCL hatch; both strict readers surface uid/dev/ino; the transfer temp sibling is excluded from git. Bridge: a parked deferred restore prompt now counts as active in getSessionSummary and the coalesced-restore waiter (never in the restore response, which the route reads); new reset-pending barrier plus clearSessionWorktree / severSessionClients surfaces. Channels/SDK: DaemonSessionClient.resetWorktree, capability-gated DaemonChannelBridge.resetWorktreeSession, SessionRouter transfer + superseded redirect with registry self-heal, manager reset with the busy refusal and one automatic interrupted retry, and the /clear message surface. Also logs a failed worktree removal in the R8-1 generation-closed rollback instead of swallowing it. * docs(channels): correct the R8-1 rollback rationale in the Part 4B design The disposition paragraph explained the fixed block with the spawn-failed outer catch's rationale ("under !spawnCompleted no session exists"), but the block this PR changes is the post-spawn generation-guard catch, where the session exists and orphan removal is attempted. Its actual rationale is that relocation has not been attempted yet, so no session has entered the worktree and nothing can own the checkout. An implementer reading the normative disposition record would otherwise carry the wrong invariant into the reset rollback design. * fix(channels): register loop tools for a worktree reset replacement resetWorktreeSession attached the replacement session without the channel-loop MCP reconciliation that newSession and loadSession both run, so a task reset through the ownership transfer silently lost its channel_loop tools until the next handler registration sweep. Mirror the loadSession shape, including the enableChannelLoops opt-out. * docs: align worktree-reset error taxonomy names with the implementation The SDK doc comment and the protocol reference still carried the pre-implementation code name worktree_reset_not_worktree_session and described corrupt-state failures as untyped 500s. The route ships the design's taxonomy: worktree_reset_unsupported and a bounded, non-destructive worktree_reset_invalid_state 409 whose specific reason goes to the daemon log. * fix(cli): register the worktree-reset route and tag in the contract surfaces Three drift guards caught the new surface that targeted test runs missed: the telemetry route catalog now lists POST /session/:id/worktree-reset (and its drift-guard count moves to 66), the integration baseline capabilities list gains session_worktree_reset_v1, and the daemon index capability count moves to the registry's actual 154 (the doc was already one behind before this tag landed). * fix(cli): update the telemetry route catalog audit counts for worktree-reset The second drift guard (telemetry.test.ts) audits the catalog's size and attribution split; the new POST /session/:id/worktree-reset entry moves them from 65 routes (63/2) to 66 (64/2). * fix(channels): address round-3 review of the worktree reset implementation Fifteen criticals from the round-3 review pass, plus its documentation accuracy findings and the two defects those exposed. Transfer protocol: - resume-path link agreement no longer consults the replacement's transcript record, so a cold-start committed transfer resumes instead of being refused on every retry - an absent marker reads as pre-commit only while the replacement is dormant; a live replacement is the committed owner and is refused before any destructive write, rather than dismantled and double-written - the worktree-keyed ownership lock covers every non-live Part 4A restore, not just Channel-sourced ones, so a source-less restore cannot attest exclusive ownership while a transfer moves it - severing a superseded session issues one detach per remaining registration (they are refcounted) and bails when a pass makes no net progress, so a reconnecting client cannot spin a request that holds the ownership lock - the interrupted 409 no longer carries a replacementSessionId that named the caller's own session Channel bookkeeping: - a failed reset evicts the old id from the live set, without which the superseded redirect can never heal a post-flip failure - healed replacement ids survive close()'s rollback path, resumeReserved's dispatch, and every registry write - the two restore-surface recovery messages moved to the shared named-session error surface, which is where their producers actually emit; the manager's retry loop around codes the reset route never returns is gone Readers: an interactive --resume refuses a sidecar naming a replacement and preserves it as recovery evidence; a parked deferred restore prompt now also reads active on the daemon status snapshot. Docs and SDK docstrings corrected to the shipped contract: no caller registration on the reset response, marker-missing repair is a reset rather than a restore retry, invalid_state is non-destructive only for what the request itself started, the design names the renamed ownership lock and puts the superseded redirect where the code resolves it, and the reset route's dead clientId parameter is removed. * test: pin the worktree transfer invariants round 3 left uncovered Three of the round-3 coverage findings guard invariants the whole transfer protocol rests on, so they are worth their tests rather than a follow-up: - the marker transfer's two commit-window guards: a marker owned by a different uid is refused before anything is staged, and a marker swapped between the opening read and the rename aborts the commit instead of clobbering the racer - the resume path's link-agreement refusals on both sides of the flip: a missing replacement sidecar pre-commit, and a replacement that disowns the old session post-commit, each fail closed and leave the interrupted state untouched for repair - a failure after the marker flip never compensates backwards: the sidecar links stay, the replacement is not orphan-reaped, and the marker still names it Test-only; no production code changed. Each was mutation-verified by removing the guard it pins and confirming the new test goes red. * fix: address round-4 review of the worktree reset transfer Fifteen criticals from the round-4 review, verified against the code before changing anything. The sixteenth was a false claim in the PR description rather than a defect, and the description is corrected separately. Transfer protocol: - the old sidecar's backward link is durable before the replacement's forward link, so every pre-flip crash window lands on a shape the resume classification can observe instead of stranding a replacement no retry sees - the superseded redirect is decided from a re-read taken under the ownership lock, not from the pre-lock read that only supplies the lock key - the marker primitive distinguishes committed from not-committed across its own close tail, so a committed flip whose close rejected is never rolled back destructively - severing reports whether the old session is actually gone; a survivor keeps the barrier armed and is reported on the wire instead of being papered over by a 200 Fence and ownership: - the reset barrier covers every writer that reaches the checkout or the session cwd, not just prompts - the stale-marker hatch in exit_worktree no longer reads a retained superseded sidecar as ownership of the replacement's checkout - detaching a managed session releases it regardless of the live flag a failed reset drops Channel bookkeeping: - a queued turn bound to a superseded id follows its task onto the replacement, so later turns of the same chat are not silently dropped - worktree recovery advice names the task and recommends clearing only when the broken task is the selected one, so it cannot direct a transfer at a healthy task - a non-Channel cold restore whose prompt the bridge fired keeps its worktree instead of failing closed and killing the session Contract text narrowed to what the code decides: the superseded redirect is a link-driven hint to verify rather than proof of ownership, crash safety is stated per window, the reattach heal does not gate the retry it performs in the same call, the SDK's self-heal claim enumerates the shapes that heal and says the rest do not converge, and the reset response is documented as carrying a minted client registration on a fresh transfer. * fix: address round-5 review of the worktree reset transfer The ten Critical findings from round 5, each verified against the code before changing it. Nine are fixed here; the tenth is a false claim in an earlier commit message and is corrected in that message. Route: the reset barrier now survives the marker flip. Once ownership has committed, releasing the barrier stops being this request's to do, so a post-commit failure leaves the superseded session fenced for the retry that finishes the severance instead of re-admitting a writer to a checkout the marker already handed to the replacement. Both rollbacks confirm the interrupted replacement's removal before unwriting the links, and refuse when it survives rather than spawning a second writer beside it with nothing on disk naming it. The resume rollback unwrites the forward link first, matching the fresh path's compensation, so a crash mid-rollback leaves the backward link a retry refuses for repair instead of a forward link nothing scans for. The route also stops at a disconnected caller before the flip and lets the pre-commit rollback reap the replacement; `res.writable` is an own data property that stays true after a disconnect, so the guard reads `res.destroyed` and `res.socket.writable`. Channels: a superseded-redirect heal that cannot persist the registry no longer rewrites a successful load into a reported load failure whose message asserts the opposite of what happened. A pre-flip reset failure records the surviving bridge binding so the recovering load releases it before attaching, instead of being aborted by the session-replaced notification its own attach would emit. Bridge: the workflow-task and goal-control writers join the barrier. Both start work in the session cwd without passing prompt admission, and neither needs a client registration to be reached. Core: the missing-marker hatch stages an fsynced sibling and publishes it with link(2), so the marker path never exists without complete content and the exclusive create leaves no 0-byte window. The publishing link is the compare-and-swap against absence, so exclusivity is unchanged; the hatch now requires hardlink support and fails closed where it is unavailable. Docs: the restore prose names the cold-restore branch that still returns unattested worktree metadata instead of claiming an unqualified fail-closed rule, the barrier prose enumerates the eight fenced writers and says plainly that the fence is not everything that reaches the child, and the marker mechanism, barrier-release and rollback-order descriptions match the code above. * docs: mark supersededSessionLive as diagnostic-only R5-11 and the maintainer's real-daemon verification both note that the field is written on two response paths and typed in the SDK, while no first-party caller reads it. Say so where a reader looks for the contract instead of leaving the SDK comment stating an obligation nothing honours: what keeps a surviving superseded session out of the checkout is the armed barrier plus the on-disk sidecar link, not this flag, and the channel worker reports the same success message either way. External callers that need to tell the two cases apart can still read it. --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
50b33052cd
|
feat(goal): show what a Goal has spent against the window it is allowed (#11248)
* feat(goal): show what a Goal has spent against the window it is allowed A Goal's autonomous spend window is enforced but invisible. `tokensUsed` and `tokenBudget` have been on the record since budgets landed, the 30M default stops the Goal and hands it back to the user, and nothing anywhere shows how close it is to that. A `tokensUsed` grep across the TUI and Web Shell returned only test fixtures. The user watched the turn count climb with no way to tell a Goal two percent through its window from one about to stop. The footer pill, the ink status card, its OpenTUI counterpart, and headless TEXT status now report the spend. The pill and the cards abbreviate (`1.2k/30.0m`) through the existing `formatTokenCount`; TEXT spells the figures out, since that output is read in scrollback and piped into scripts, neither of which is helped by `1.2k`. Three display rules, the same at every site. A Goal that has not billed a turn shows nothing: a fresh `0/30.0m` says nothing the status has not already said, and the pill has little room to say it. A Goal with no budget shows what it spent, which is the whole of what is known. A stopped Goal keeps showing its spend, because what a paused or blocked Goal cost is exactly what the user is deciding about. The OpenTUI card computes its own subtitle from its own snapshot type, so both were extended alongside the ink card rather than left to drift apart under the parity gate. `formatGoalState` is exported to be pinned directly: the states worth checking are far cheaper to construct as snapshots than to drive a headless run into. The figure is the Goal meter's own scope -- the model calls a Goal makes in its own turns, excluding subagents and the verifier's checks -- and the user doc says so. * docs(goal): say that resuming a spent Goal adds a window rather than resetting Resuming sets the budget to what the Goal has spent plus another grant, so the pill reads 30.0m/60.0m straight after a resume. The paragraph said the window is what the setting says, which holds only for the first one. Also narrow the no-budget card assertion, which read as "no slash anywhere in the frame" and would have failed on a slash in the objective. |
||
|
|
be9d5e8d3f
|
feat(external-context): Add opt-in auto recall for administrator-owned Mem0 dialects (#11246)
* feat(external-context): add opt-in Mem0 auto recall Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(external-context): bound auto recall process lifetime and sanitization Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(external-context): separate hook test startup and execution budgets Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |