mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
main
3085
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5b44cfad71 |
fix(web-shell): include channel sessions in release dialog (#16537)
* fix(web-shell): include channel sessions in release dialog The release dialog inherited the sidebar's default source filter, which excludes sourceType: "channel". A workspace filled by idle channel sessions therefore opened an empty recovery list. Query the workspace catalog without a source filter for this dialog and keep the default, channel, and qwen-live rows. The sidebar and every other useScopedSessions caller keep the default filter. Add hook/dialog regressions and verify banner -> release -> create with a real sourceType: "channel" session filling a one-session workspace. * fix(web-shell): keep live sessions visible in release dialog Expanding the recovery list from the sidebar's default source filter to persisted history can still bury the sessions holding slots: closed channel rows fill the first page before they are reached. Add a liveOnly session-list option on the daemon route and SDK so callers can read the workspace bridge instead of persisted history. The release dialog requests the shared page size with that option and keeps listing default, channel, and qwen-live rows, so closed history can no longer crowd out what can be released. Co-authored-by: Qwen-Coder <[email protected]> * fix(sdk): serve liveOnly session lists over native REST The ACP session/list mapping drops liveOnly, so a DaemonClient configured with an AcpHttpTransport silently returned persisted history for the release dialog's live-only request. Route liveOnly through the native REST transport for both the legacy and workspace-qualified list methods, and cover the real ACP transport with a regression test. Co-authored-by: Qwen-Coder <[email protected]> * test(web-shell): pin the release dialog's source whitelist Live-only listings can include side_task, scheduled_task, and standalone sessions that the server does not hide. Add those rows to the dialog test so widening the whitelist fails, and state the 100-row live-page ceiling next to the page-size request. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
d0b80d4a99 |
fix(memory): match reader advice to the binding index budget (#15368)
* fix(memory): match reader advice to the binding index budget * fix(memory): account for writer notices and combined limits * fix(memory): bound description advice to recoverable index space * fix(memory): keep uncertain index advice conditional * fix(memory): preserve durable budget remedies and complete links * fix(memory): keep regeneration budget advice conditional * test(memory): align writer integration with reader line-count advice |
||
|
|
708ba347d3 |
feat(ci): add disposable external PR runners (#16513)
* feat(ci): add disposable external PR runners * test(ci): align platform lanes with disposable runner routing --------- Co-authored-by: yiliang114 <[email protected]> |
||
|
|
d85e911c78 |
fix(cli): restore OpenTUI dialog editing controls (#15908)
* fix(cli): restore OpenTUI dialog editing controls * fix(cli): preserve dialog Ctrl+C editing ownership * fix(cli): clear OpenTUI Source fields on Ctrl+C * fix(cli): flatten pasted breaks and read live focus in OpenTUI dialogs * test(cli): pin column-exact dialog rows and the second-Ctrl+C exit * test(cli): correct the row-oracle comment to the measured geometry * test(cli): name the measured phantom-column shape in the Sources oracle --------- Co-authored-by: Shaojin Wen <[email protected]> Co-authored-by: Shaojin Wen <[email protected]> |
||
|
|
cf29f90062 |
fix(ci): route the review/resolve permission gate to the ECS pool (#16482)
* fix(ci): route the review/resolve permission gate to the ECS pool The authorize job gates every @qwen-code /review and /resolve command, but its ECS routing required github.event.pull_request, which issue_comment and workflow_dispatch payloads do not carry. Every command therefore fell through to ubuntu-latest, and the gate sat in the hosted queue for hours (PR #15286: 3h10m for a 4-second job) while the ECS pool was idle. Route issue_comment and workflow_dispatch to the persistent ECS pool, matching qwen-autofix.yml's route/review-scan pattern for PAT-bearing API-only steps. authorize runs no agent, checks out nothing, and processes no PR content, so this does not widen the fork attack surface. PR-payload events keep the same-repo fork guard; the hosted fallback and ECS kill switch are unchanged. resolve-pr and publish-resolution stay on hosted by design (ephemerality); this removes the gate wait only. * fix(ci): harden the authorize ECS-routing pins and risk docs Address review feedback on the gate routing change: - Pin the checkout-free invariant the ECS routing depends on: a uses: step on authorize would execute contributor-authored files on the persistent pool with CI_BOT_PAT in the step env while the expression pin stayed green (R1-1). Scoped to authorize; mutation-witnessed (adding actions/checkout reds it). - Record the trigger-set widening in the design-doc Risks (both languages): issue_comment makes the gate fireable by any GitHub user on a fork PR, and the should_review authorization fact is now produced on the shared host and consumed downstream without re-deriving (R1-2). - Correct the precedent wording: route loads only the read-only github.token; review-scan is the job that loads CI_DEV_BOT_PAT on the pool. * fix(ci): harden the shared-pool authorize gate against gh config reroute Address the round-2 CHANGES_REQUESTED review (R2-1 Critical, R2-2): R2-1: routing the gate to the persistent ecs-qwen pool moved its CI_BOT_PAT gh api call onto a host with a shared, writable ~/.config/gh. A co-resident survivor could plant http_unix_socket there, receive the PAT, and forge the permission response downstream consumes as should_review. Prepend the same pins as qwen-autofix.yml's route/review-scan — export GH_HOST=github.com plus a private, guarded GH_CONFIG_DIR (a failed mktemp exits 1; the gate fails closed). No `unset GH_TOKEN`: it is this step's only credential. Witnessed with real gh 2.103.0: the as-written step leaks the PAT to a planted socket and approves a stranger; the hardened step denies with the socket untouched. R2-2: extend the checkout-free guard to the run: half — pin the step inventory to the single permission step and forbid `${{` spliced into any run body (event values must arrive via env:). Mutation-witnessed: removing the preamble and adding a run-only second step each red the test. Also correct the design-doc parity claim (both languages): the exposure matches review-scan only WITH these gh pins; document the hardening. * fix(ci): extend the ECS gate routing to triage and harden the gate pins Two batches, verified together before push: 1. Extend the command-gate routing to qwen-triage.yml's authorize (the /triage, /tmux, /verify sibling blind spot): the same issue_comment / workflow_dispatch disjuncts, and — because that job has three steps that spend CI_BOT_PAT on gh — the same self-contained GH_HOST + guarded GH_CONFIG_DIR preamble on each, per the repo's per-step convention. Raise the recorded workflow size baseline to match the added hardening. 2. Address round-3 review (R2-2 fix-induced, R3-1): the gate pins tested substring presence only. Now evaluate the comment-stripped live lines and pin the guarded mktemp form + fail-closed shape, require GH_HOST / GH_CONFIG_DIR exported before the first gh api, and forbid executing repository code via relative paths or interpreters (the pool cwd is the reused $GITHUB_WORKSPACE). Applied to both the review and triage gate tests. Mutation-witnessed: an unguarded one-liner, a relocated preamble, and an appended `node ./…` line each red the tests. Tests: qwen-pr-review-workflow.test.js 271, qwen-triage-workflow.test.mjs 131, ci-runner-routing.test.mjs 103 — all pass; prettier + ESLint clean. * fix(ci): supply RUNNER_TEMP to the triage gate replay tests; harden the gate pins R4-1 (Critical): the new ${RUNNER_TEMP} gh-config preamble is the first RUNNER_TEMP reference in the triage permission step, and three replay harnesses in scripts/tests/qwen-triage-workflow.test.js execute that body verbatim without setting RUNNER_TEMP — off-CI (npm run test:scripts) the fail-closed guard exits 1 before writing should_run. Set RUNNER_TEMP to each harness's existing mkdtempSync dir (the :1773 precedent); the workflow's fail-closed guard is unchanged. Verified: the file is red under `env -u RUNNER_TEMP` before and 203/203 green after; dropping the line reds it again. (The review named two harnesses; running the full file surfaced a third.) Also clear the round-4 deferred notes: - R4-2: the /verify ack comment no longer promises a receipt "even when the sandbox lane is backed up" — the gate now also queues with the pool. - R4-3/R4-4: the triage routing pin is now an exact expression equality, the step inventory is pinned, and PAT-bearing steps are identified by env GH_TOKEN rather than gh subcommand spelling. - R3-1: tighten the no-repository-code deny-list (quote boundaries + interpreter as a word) so `bash -c './x.sh'` no longer slips past; mutation-witnessed in both the review and triage gate tests. Tests: triage vitest 203 (off-CI), triage .mjs 131, review vitest 271, ci-runner-routing 103 — all pass; prettier + ESLint clean; size gate green. * fix(ci): correct the gate-routing narrative; close the round-5 verify gaps R5-1 / F1 (narrative): the motivating incident is a /resolve, whose end-to-end latency this PR does not change — resolve-pr stays on ubuntu-latest, so only the gate wait is removed. State this in both design-doc languages and the pinning test comment; the saving lands on /review, /triage, /tmux, /verify (pool-routed downstream). Correct the "cancelled without ever running" sentence to the run's timeline (resolve-pr started when the gate finished and was cancelled ~29m36s later). S1: the no-repository-code deny-list missed absolute-path interpreters (/usr/bin/node x.js) and versioned Python (python3.11 x.py); widen the regex (optional path prefix + python[0-9.]*) in both gate tests. Mutation-witnessed: both previously-surviving mutants now red, clean tree green. S2: each hardened gh step minted a $RUNNER_TEMP/*gh-config.* dir that nothing removed; add trap 'rm -rf "$GH_CONFIG_DIR"' EXIT to all four steps (RUNNER_TEMP survives between jobs on the pool). S3: record the exact final workflow sizes in .size-baseline. Tests: triage vitest 203 (off-CI, RUNNER_TEMP unset), review vitest 271, triage .mjs 131, ci-runner-routing 103 — all pass; prettier + ESLint clean; size gate green. * fix(ci): pin the no-repository-code invariant on every triage gate step The triage gate meta-test applied the relative-path/interpreter denylist only to PAT-bearing steps, so a repository-code execution line added to the credential-free budget step survived the suite (witnessed by mutation). The no-repository-code invariant is a job-wide safety premise of the ECS routing: even a credential-free step that runs a planted file leaves a survivor on the persistent host. Pin it on every step's comment-stripped live lines, and align both design-doc versions with the widened coverage and the witnessed mutation set. Co-authored-by: Qwen-Coder <[email protected]> * docs(ci): correct the routing-matrix coverage claim for the triage gate The validation plan said the triage gate is pinned by the triage meta-test AND the ci-runner-routing matrix, but the matrix never reads either authorize job (it pins the sibling pool jobs). State the actual coverage in both language versions. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
4c8ebeab4d |
feat(core): view one pull request by URL for the PR-state watch (#16530)
* feat(core): view one pull request by URL for the PR-state watch Add fetchGitHubPullRequestView(url, cwd, env?), the single-PR view the daemon's PR-state watch will use (step two of PR A in docs/design/2026-11-04-daemon-pr-state-transitions.md, section 1). It runs `gh pr view <url> --json url,title,headRefOid,mergeable,state, statusCheckRollup` and returns the canonical URL, title, head SHA, open/merged/closed, mergeable (absent while GitHub reports UNKNOWN), the CI state and the failing check names (`<workflow> / <job>`, the check name or the status context; once each, at most 20). Only an http(s) /owner/repo/pull/<n> URL is accepted, and it is rebuilt without credentials, query, fragment or tab path before it reaches gh, so an issue URL, /PULL/7 or /pull/0 never becomes a branch lookup. The working directory must exist, so a missing one is not reported as gh being unavailable. Failures carry a reason (not_a_pr_url, cwd_missing, gh_failed, timed_out, unparsable). There is no host check; the caller restricts URLs to the allowed GitHub hosts. aggregateChecks is exported with unchanged behaviour; its failing-entry predicate is shared with the failing-name extraction. Every rollup entry still counts: gh's JSON exports no event, app or workflow id, and a dedupe by names and times would let a skipped or other-trigger run hide a current failure. The design doc's Files-affected row for core names the real signature and fields. * fix(core): report gh's diagnostic, not Node's argv, when gh fails execFile's callback error has no stderr property (only the promisified form attaches it), so ghErrorMessage fell back to Node's "Command failed: <argv>" message: a failed PR list, view or create reported the gh command line (for create, the PR title and body) ahead of gh's diagnostic, which the 4,096-character raw cap and the route's 512-character display cap could then cut off. The three callback wrappers now attach the callback's stderr, as the PR issues wrapper already does. When gh exits or is signalled with nothing on stderr, the message names the exit code or the signal instead of falling back to the argv, and a failure Node raises itself (a string code such as a spawn error or an overflowing buffer) keeps Node's message even if gh wrote something. `gh api graphql` failures of the PR issues fetch, which share the helper, get the same treatment. The test helper now fails the way execFile does: an exit 1 unless told otherwise, with stderr only as the callback's third argument, so the stderr tests exercise the wrappers and the stderr-over-exit-code order rather than the mock. Tests cover a silent exit, a signal, a buffer overflow with stderr, and a silent create that must not echo the title and body; the view's failing-names test gains a failing check run with an empty name and a status context with an empty context, which fail the head but are not named. |
||
|
|
978d9a23c3 | fix(web-shell): respect optional transport capabilities and local turn timing (#16536) | ||
|
|
0807f5d391 |
refactor(channels): share the bound-session prompt contract via channel-base (#16523)
* refactor(channels): share the bound-session prompt contract via channel-base Move the GitHub channel's operator instruction template contract (key validation against a caller-supplied event vocabulary, template validation, the role/event/lane resolution chain, the %var% renderer) and the bound prompt envelope (platform header, untrusted-data and not-published notices, labelled trusted-instruction block, sanitised data lines, Role line, visible 6,000-code-point body cap) into @qwen-code/channel-base, so the daemon's PR-state watch can reuse them instead of keeping a copy (step one of PR A in docs/design/2026-11-04-daemon-pr-state-transitions.md). The adapter keeps its vocabulary, the deliverToBoundSessions precondition, the comment-lane class, metadata parsing and provenance line, and its output is unchanged except for two rendering fixes: %constructor%-style names now stay literal like any other unknown name (they rendered Object.prototype members), and every substituted value is sanitised, as the templates design specified (only %title% and %sender% were; those two are still also capped at 200 code points). Tests pin the legacy, role-aware comment and role-aware watch prompts byte for byte (strings captured from the previous adapter), %url% on a GitHub Enterprise origin with a port, and the exact validation messages. The design docs, the template editor design and the user guide are updated to match. * fix(channels): keep the trusted instruction label inside the tag peel window The bound prompt labelled its trusted block `[Operator instruction — trusted, from <source>]`. With the channel's source that label's content is 65 characters, one over the 64 that sanitizePromptText peels from a start-of-line tag, so an event body that began with a copy of the label kept its brackets and read as a second trusted block. The label is now a fixed `[Operator instruction — trusted]` with the source on a `Source:` line under it, so it stays peelable whatever source a producer names. The platform the header names becomes a closed list (BOUND_EVENT_PLATFORMS) for the same reason: the header's content is the name plus 36 characters. A test runs over that list and feeds a copy of the header and of the label through the body and a data line; another puts a copy at the start of a substituted value. Also from review: pin the untyped-winner guard of the resolution chain, empty values rendering as empty, and misspelt `watch:` types being rejected, and align the templates design's validation plan with the cap that applies only to %title% and %sender%. The templates design, the daemon design and the user guide show the new label; the templates design's prompt example gains the not-published notice it was missing, and the daemon design's validation plan now expects only the prompt without a template to stay byte-identical. |
||
|
|
ced03aef5d |
feat(serve): reclaim idle channel sessions (#16532)
Channel workers keep an SSE event pump attached to every cached route, so the idle reaper treated those sessions as actively watched and never reclaimed them. A workspace could fill every maxSessions slot with sessions nobody was using and then reject new creates with 503. Let the reaper look past a channel-owned subscriber while preserving the local-work, active-prompt, and child conditional-close guards. Surface session_limit_exceeded as a banner with a release action. The release dialog falls back to the workspace daemon client when it opens from the empty new-session screen, where no session client exists. Design: docs/design/channel-session-idle-reclaim.md |
||
|
|
61aef512e7 |
feat(web-shell): offer a details side task from pending AskUser questions (#16458)
* feat(web-shell): offer a details side task from pending AskUser questions While an AskUserQuestion is pending, the Web Shell hides the composer and the btw shortcut ignores the pending-approval state, so a user who does not understand the options can only answer blindly or ignore the question. Add an "Ask for details" action to the pending panel. It opens a side task whose prefilled prompt snapshots the question and options and asks for a verified explanation — background, per-option meaning, differences, impact and risks, and a recommendation — delivered as a self-contained single-file HTML in controlled ASD-STE100-style sentences under .qwen/tmp/, previewable in the right panel. The side task inherits the parent context but never answers the question or touches the main session; the question stays pending until the user answers or ignores it. The action is main-view-only and gated on the session_side_task capability, matching /btw side. No daemon, protocol, or settings changes. * fix(web-shell): one details side task per question, reachable on narrow panels Post-publication audit of the AskUser details action found three issues: - Repeat clicks provisioned duplicate side tasks for the same pending question, each burning a model turn. Give the details tab a stable id derived from the parent session and the permission request id and upsert it, reusing the artifact panel's open-or-focus pattern; /btw side keeps its unique per-invocation draft id. - The question snapshot dropped multiSelect, so the explanation prompt did not say options can combine. Append a [multiSelect] marker (strict true). - On a 320px two-question panel the actions row overflowed to the left and pushed the details button out of the panel (left: -103px in a real-browser probe; scrollWidth misses left overflow on a flex-end row). Let the row wrap and pin a narrow-viewport regression test asserting every action button stays inside the panel and stays clickable. Each fix carries a red/green witness: the repeat-click App test, the multiSelect unit test, and a flex-wrap mutation of the narrow visuals test. * fix(web-shell): keep the details tab id within the daemon clientId budget The dedup fix derived the details tab id from the parent session id AND the request id. SideTaskPanel derives the pane's daemon clientId as side-task:<parentSessionId>:<tabId>, which with two uuids reached 138 chars — over the daemon's MAX_CLIENT_ID_LENGTH (128). The child session load then 400s, the pane never connects, and the prefilled prompt is never sent. Caught by re-running the real-daemon scenario on the post-fix head; mocked unit tests could not see it. Derive the stable id from the permission request id alone (a uuid; tabs are already bucketed per parent session), bringing the clientId to 101 chars. Add a SideTaskPanel regression test asserting the derived clientId stays within 128 for uuid-length inputs; the old id shape fails it with 138. * fix(web-shell): retain the details question-to-child association across tab close Review found that deduplicating only against open tabs loses the existing child when the details tab closes: clicking "Ask for details" again for the same pending request provisioned a second child, and reopening the child from the side-task catalog first did not deduplicate either (its tab id is side-task:<sessionId>, not the details-request id). Persist the pending-request-to-child association per parent session in localStorage, written when the details child is created. On click, focus whichever open tab shows the known child (details tab or catalog tab); with no tab open, reopen one attached to the existing child session — no draft, no initial prompt, so nothing is provisioned or sent again. One entry per parent supersedes the resolved question's association, keeping the store bounded; the association also survives reloads. Regression tests cover both reported paths (close + re-click keeps exactly one createSideTaskSession call and reopens with the child's sessionId and no initialPrompt; catalog reopen + click focuses the single existing tab). Red witness: dropping the association lookup fails the close + re-click test with two creation calls. * fix(web-shell): close the details association lifecycle gaps Re-review of the association persistence found three lifecycle gaps, all fixed here with regression tests and mutation witnesses: - A child exists before its explanation prompt is admitted. Reopening a closed details tab now retains the prompt on the tab; the pane's existing empty-session guard delivers it exactly once (only while the child has no user prompt) and never re-sends it. - Archiving a details child left its persisted association pointing at an unloadable session (the daemon's load route 409s session_archived). archiveSideTasks now invalidates associations pointing at archived children, so the next click provisions a fresh child. - Closing the draft tab before creation resolved skipped the association write, orphaning the child and provisioning a duplicate on the next click. The parent identity is captured when creation starts, and the write no longer depends on the tab still existing. Also pin two invariants the deep-verification report found unasserted: the derived clientId budget with uuid-length inputs through the real tab-id derivation (App test), and the formatter's array guard against non-array objects (formatter test). * fix(web-shell): supply the unsent details prompt to a catalog-reopened tab Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): treat media-only user prompts as interaction in the side-task guard Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): validate details child archive state at the read site and address review suggestions Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): replace stale details tab on fresh draft and re-check pending after the liveness query Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): replace a details tab bound to a different child than the validated binding Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
5b4dcbad19 |
fix(channels): watch every open bound PR and keep polling during public turns (#16502)
* fix(channels): watch every open bound PR and keep polling during public turns The PR state watch selected bound PRs purely by binding recency under a hard cap of 30, so older open bindings were never polled once newer bindings (including already-merged ones) filled the budget, and merged bindings outside the cap never received the terminal observation that would retire them. Selection now watches open PRs first and drains terminal-pending bindings with the leftover budget; the cap becomes the configurable prWatchLimit (default 30, range 1-500, fractional values round up), exposed in the channel descriptor and management API. Public-path tasks (review requests and assignments on unbound PRs) ran their full agent turn inside the poll loop, freezing watch detection, bound delivery, and every other notification for the turn's duration (observed: 64 minutes on a production daemon). They now execute in a background FIFO runner (concurrency 1); claims stay durable and synchronous, the cursor gate and crash recovery keep their semantics, and bound/watch deliveries stay inline. The first-contact body feed gains a thread-level gate so an async body turn cannot overtake and steer-cancel a still-retrying comment turn. * fix(channels): close review gaps in watch coverage and the async task runner Review round on the bound-delivery reliability fix. Three correctness findings, each reproduced against the base: - The first-contact body deferral gate could lose the body permanently: nothing reserves a deferred feed, and the end-of-poll cursor commit can mark the thread read once the blocking task settles. Remove the gate; the runner FIFO already serializes the body behind the comment turn. The absence gate now runs synchronously at dispatch so backpressure deferrals keep the false verdict the lane's body suppression needs. - disconnect retired the runner generation but the winding-down runner's restart hook drained the queued tasks anyway. Guard the restart by generation and resolve idle waiters at disconnect. - Recovery could re-resolve a backpressure-parked task whose public turn was running (the running transition merges patches) and deliver it bound a second time. Skip in-flight ids before any recovery branch mutates a task; crashed tasks without an in-flight entry still recover. Also: gone-session public fallbacks requeue through the serial runner instead of awaiting the turn in the poll lane (head-of-line block and steer-cancel on shared sessions); a same-instance reconnect keeps the orphaned runner's executing id in flight so recovery cannot double-run it; held threads skip redundant comment re-fetches via a poll-start snapshot; escaped runner failures are journaled once per task; the watch budget reserves a terminal slice (20%, at least one) so the drain progresses even when open PRs fill the limit; stale-open bindings tier as terminal once the watch observed closed; the limit constants are exported for the descriptor, the field is registered and translated in the Web Shell editor, and validation enforces the bound-delivery prerequisite; the rate arithmetic is documented at every surface. * fix(channels): drain terminal bindings outside the open watch budget The in-budget terminal reservation excluded up to ceil(limit*0.2) open bindings while a merged/closed backlog drained, permanently swallowing edge events on those bindings — the defect this branch fixes. Open PRs now take the full prWatchLimit; terminal-pending bindings drain in an additional slice outside it (20%, at least one, plus leftover budget), bounded at limit + ceil(limit*0.2) pulls.get per poll during the drain. Pins the verification fixture (30 open + 5 newer terminal at limit 30: oldest open observed, its head change delivered) and keeps the removed in-budget variant as a mutation witness. Rate arithmetic updated on the descriptor, i18n (EN/ZH), user guide, and bilingual design docs. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
4c5820c745 |
fix(web-shell): close out managed-host review threads from #15928 (#16203)
* fix(web-shell): close out managed-host review threads #15928 merged with 19 review threads left open. This lands the code fixes and the tests that actually pin them. Code: - shadowDom: the ref-counted release is now idempotent, so calling one install's cleanup twice no longer strips a sibling shell's stylesheet (R2-4). - ManagedArchivedSessions: the host status callback is held in a ref, so a host that stores the status in state no longer re-emits on every render and loops (R2-7). - ManagedSessionsPage: the pane measurement re-runs when the host supplies a session id after mount, so a host that starts in the waiting state still gets a ResizeObserver and the inline task panel (R1-2). - ManagedSessionsPage: the pending-owner link goes through openSession, so it clears the draft/error of the conversation being left and forwards the owner's workspace id (R1-13). - ManagedAgentWebShell: `baseUrl` is optional when `managedAgentProvider` is injected, and a missing one fails loudly instead of resolving requests against `<origin>/undefined` (R1-11). Tests: - shadowDom: a double release keeps the sibling sheet (R2-4). - ManagedArchivedSessions: a fresh inline callback settles (R2-7). - ManagedSessionsPage: pane measurement after a late session id (R1-2), pending-owner navigation carries the workspace (R1-13), the `:host:` pending key keeps sessions apart (R1-4), the empty-state test uses the real failure shape and asserts banner copy instead of a bare truthiness flag (R1-5), the welcome block is asserted absent next to a failure banner (R1-8), and a foreign error banner is dropped on session switch (R1-9). - ManagedAgentWebShell: no provider and no baseUrl fails loudly (R1-11). - managed-host.dom: the pending row assertion matches the row's actual copy instead of a substring it no longer contains (R1-7). Every new assertion was verified red with the fix removed. * fix(web-shell): keep the pending owner's workspace and late status callbacks Three review follow-ups from the #15928 threads: - The pending-owner link resolved the owner's workspace from the served session page, which is filtered, paginated and empty in host mode. When the owner was absent the click forwarded no workspace and the host's handler then cleared the selection. Capture the workspace id on the pending record at submit time and prefer it, keeping the page lookup as the fallback for records written before this change. - The `baseUrl` guard tested truthiness, so `baseUrl=""` — a supported same-origin value that resolves to the page origin — threw during render. Only a missing prop is a configuration error now. - Holding the archived-status callback in a ref keeps the loop closed but also dropped delivery to a host that supplies the callback only after the list settled. Re-emit on the absent -> present transition while keeping identity churn from re-running the effect. Each case has a regression test that fails without its fix. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw0nuit6aw * style(web-shell): apply prettier to ManagedArchivedSessions test The Lint & Static check fails on prettier formatting of the late-status callback's parameter type annotation. Formatting-only change; eslint and prettier now pass on the file. * fix(web-shell): close out managed-host R2 review threads - reject a null baseUrl, not only an absent one: a plain-JS host erases the prop type, so an unresolved config value arrives as `null` and every request resolved against `<origin>/null` - state the requirement in the README and both design-doc languages, which still promised that omitting the provider preserves Java behavior - pin the pending-record write half (the submitted workspace) and the readPending rejection of a non-string workspaceId; both tests fail when the guarded line is removed Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw0s4ubab2 * fix(web-shell): render the shell fallback for a missing baseUrl and pin banner clearing The `baseUrl` guard throws from the shell's own render body, above the ErrorBoundary it returns, so a host mounting before its config resolves saw an empty container instead of RootErrorFallback. The provider now resolves one level below an outer boundary, and the pending-owner navigation test also pins the `setError(undefined)` half of openSession. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw4plfohgl * fix(web-shell): name the shell fallback and assert the contained baseUrl failure The outer config boundary returns an unnamed arrow, which `react/display-name` rejects, so pin its name. The two baseUrl-guard tests still asserted the pre-boundary contract (the guard's throw escaping to the host); the boundary now reports that failure through the shell's own fallback, so assert both halves: the fallback renders and the guard's message still reaches `console.error`. The pending-owner test's added banner pin is reverted: the navigation error renders only when `interactive` is false, so that path cannot show it. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw4rqlfigo * style(web-shell): apply prettier to ManagedAgentWebShell The Lint & Static lane failed at "node scripts/lint.js --prettier" on packages/web-shell/client/ManagedAgentWebShell.tsx: the props interface `extends` clause and the RootErrorFallback JSX needed reflowing. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw52gecrh3 * docs(web-shell): describe the baseUrl guard's observable behavior The guard runs below the shell's own error boundary, so a host sees the fallback render and reads the cause from console.error -- never a throw. Correct the three copies of that contract and cover the resetKeys recovery path (missing baseUrl, then a present one) with a transition test. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw56qpv9h9 * test(web-shell): pin same-origin storage key and the provider reset path R5-1: assert the constructed provider's storageKey resolves against the page origin, so coercing `baseUrl` to undefined can no longer keep the test green while requests resolve against `<origin>/undefined`. R5-2: add a provider-only re-render case so the boundary's provider reset key is pinned, not just the `baseUrl` half. R5-3: document that recovery needs a re-supplied `baseUrl` or provider, because the boundary clears only through its reset keys. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw5q15wki1 --------- Co-authored-by: root <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: Shaojin Wen <[email protected]> |
||
|
|
a12d6946c2 |
fix(managed-agent): mask digit-led passwords and refuse an empty Harness user info in the upgrade command (#16352)
* fix(managed-agent): mask digit-led passwords and refuse an empty Harness user info
Follow-ups to the review items deferred on #16110 and #16175:
- A refusal hides everything up to a URL's last @, also when the URL
parses: a password that starts with digits and holds a / ? or # reads
as a port to both the URL parser and java.net.URI, so
http://svc:2024#Secret@harness:4170 printed whole. The --server-url
query refusal masks too.
- Without the server's verdict, start refuses an @ before the Harness
host (http://@host, http://:@host), which java.net.URI reads as user
information and the connector refuses, instead of restarting the
Harness for it.
- The health row and start show a status of 0, false or "" instead of
dropping it, and print the URLs they dial through the control-byte
neutralizer.
- A Session id with a lone surrogate fails its row by name instead of
"URI malformed".
- The Flyway reader test covers a path two modules both ship; the user
guide names verify --tenant's --actor condition, and the design docs
state the masking, status and id rules as applied.
* fix(managed-agent): neutralize the DEL and C1 bytes a quoted value keeps
JSON.stringify escapes C0 controls but not DEL or C1 bytes, so a refused
URL or a Session id the verify row echoes could carry U+009B, an 8-bit
CSI introducer, to the terminal, and the id was not bounded. The four
refusals that quote a URL and the id row now go through printable()
after JSON.stringify: C0 controls keep their escapes, DEL and C1 bytes
show as U+FFFD, and the quoted text stops at 200 code points.
* fix(managed-agent): print no password a --server-url carries, and stop predicting java.net.URI
Review round 2 on #16352.
- A --server-url with an @ left after the URL parser reads no user
information is refused: it is the rest of a password that starts with
digits, which the parser reads as a host and a port, so the phase would
dial the wrong host and print the password. check judges --server-url
the same way, since its plan repeats it.
- start's three outcomes and verify's health row print every URL with its
user information masked.
- With the server's verdict false, the refusal names a reason only where
the value shows one the server gives too: a user name or password, a
query or a fragment, or an @ in an authority after //.
- Without a verdict, start takes only a plain http(s) URL, a rule of its
own and narrower than the server's, instead of modelling how
java.net.URI reads user information: http:/@host, a backslash, a tab,
an underscore or a space no longer reach the Harness restart, and an @
in a path still does.
- The design record and the deployment guide say so.
* fix(managed-agent): correct upgrade URL guards and plan output
* fix(managed-agent): keep refusing an @ in --server-url, and spell plan words
|
||
|
|
5faa620af9 |
fix(core): keep the user's instruction last in the model request (#16274)
* fix(core): keep the user's instruction last in the model request
A fresh turn's user entry is assembled from independent sources: per-turn
reminders ahead of the prompt, then hook/Mod context and the memory catalog
behind it. A long multi-part instruction therefore lands mid-entry with tens
of kilobytes of injected context on both sides, and a flash-class model
answered that it had received no task while the instruction sat there.
Reorder the request copy at the single point where every main-turn request is
assembled, moving the injected run that trails the caller's own text ahead of
it. Stored history keeps its shape: the transcript display projection and the
resume receipt both read the hook block as the final part, so reordering the
stored entry would break the projection and re-deliver hook context every turn.
Leading tool results keep their position, a non-leading tool result leaves the
entry untouched, and the volatile reattach tail stays last so the provider
cache breakpoint keeps its anchor. A reminder the caller placed between two of
its own parts bounds the movable run and stays put.
Refs #16259
Co-authored-by: Qwen-Coder <[email protected]>
* docs(core): correct the prompt-context placement prose
The Constraints bullet and the isInjectedPromptContext docstring both claimed a
caller-authored part shaped like a reminder keeps its own order. The rebuild
classifies by shape across the whole entry, so that promise does not hold for
the caller's last part: a trailing part in either envelope is treated as
injected and moves ahead of the caller's text. Describe the mechanism instead,
in the docstring and both design docs.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw1upqc7cl
* fix(core): reorder injected context on every user entry
The send boundary reordered only the pending entry, so request N sent
[reminder, hook, catalog, ...prompt] while request N+1 rebuilt the same
entry from stored history as [reminder, ...prompt, hook, catalog]. The
shared provider prefix then ended at the leading reminder and every
injected block was re-prefilled at full input price on every turn - the
failure mode REATTACH_BOUNDARY_METADATA exists to prevent (#11627).
Applying the same rule to every user entry in the request copy keeps an
entry's wire shape position-independent, so request N+1 extends request
N's prefix exactly. Stored history is still untouched, which keeps the
transcript projection and the resume receipt intact.
Pin it with a two-turn test that asserts the first turn's entry is
re-sent byte-identically, and compare each request with the previous one
instead of comparing the request prefix with stored history.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw25fj8od0
* docs(core): state the reorder's every-entry scope and name the third updated assertion
The every-entry pass landed in
|
||
|
|
db9478db8b |
fix(core): keep follow-up suggestions in Thai, Lao, Khmer and Myanmar (#16341)
* fix(core): keep follow-up suggestions in Thai, Lao, Khmer and Myanmar getFilterReason() counted words by splitting on whitespace and exempted only CJK, so a space-free Thai, Lao, Khmer or Myanmar suggestion was one "word" and was dropped as too_few_words. Count word-like segments with Intl.Segmenter for these scripts (2 characters minimum, 12 words maximum, matching the prompt). The segmenter is created lazily and only on a full-ICU Node build, because on small-icu builds segment() can crash the process (#11747) and this filter also runs outside the CLI's ICU probe; without it a 60-character bound applies. Adds the first CJK and Southeast Asian script tests for the filter. Fixes #16339 Co-Authored-By: Claude Opus 5.5 <[email protected]> * test(core): pin the segmenter path for Thai follow-up suggestions Add two cases where the word count and the 60-character fallback disagree (13 words in 37 characters; 11 words in 76 characters), so the tests fail if the segmenter is silently disabled. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(core): keep whitespace bounds for spaced Thai follow-up suggestions Use the segmenter only for suggestions without spaces. A Thai suggestion that also names a path or a command (e.g. "รัน npx vitest run ...") has few whitespace words but many segments, so it was dropped as too_many_words although the base branch accepted it. Also test that no segmenter is created when process.config reports a small-icu build. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(core): keep the slash exemption for Thai follow-up suggestions A /-prefixed suggestion in Thai, Lao, Khmer or Myanmar has one whitespace word, so it entered the segmenter branch and could be dropped as too_many_words before reaching the slash exemption. Return early for it, as the space-delimited branch does. Update the documented filter rules: these scripts are counted by word segmentation, and a single word in them is allowed. Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
f2e9a3fc66 |
feat(channels): add GitHub repository event filter with allowlist and blocklist (#16493)
* feat(channels): add GitHub repository event filter with allowlist and blocklist The GitHub channel processed notifications from every repository the authenticated account can see. The only repository-level control was the group access allowlist, which cannot express "everything except these repositories" and hides a plain event filter behind access-policy machinery. Add repoFilterMode (off / allowlist / blocklist) and repoFilter to the GitHub plugin. The filter runs at notification intake, before the reason filter, journals capped repo_filter skip rows plus one collapsed count row, and never holds the poll cursor. One shared validator rejects an active mode with an empty or malformed list at save time and at connect time; while the mode is off a stored list is ignored, so it can be parked without blocking unrelated saves. Notifications about bound PRs are filtered too; only their PR state watch events bypass the filter. The Web Shell editor gains a Repository Filter select and a Filtered Repositories list shown only while a mode is active, with the visibility and validation gated on the same predicate. Pasted repository or PR URLs are shortened to owner/repo on save; non-repository GitHub URLs are rejected instead of silently shortened. A parity table binds the editor mirror to the plugin's own validator, and the visuals suite captures the new editor in both themes. * fix(channels): close review findings on the repository event filter Address the verify-round findings and the triage review notes on #16493: - F1 (Medium): the reserved-path guard missed sibling GitHub areas, so pasted URLs like github.com/apps/renovate or /features/copilot were still silently shortened to nonexistent repositories that both validators accepted — fatal under an allowlist. Extend the guard with the measured missing roots plus the other known two-segment areas, export the set, and replace the sampled fixture with an enumerated test over every member plus explicit regression pins for the reported shapes. Removing a listed root now fails the pins; the denylist's best-effort nature is documented instead of overclaimed. - F2 (Low): pin the documented ordering — a notification excluded by both gates is journaled repo_filter, not reason_filter. The verify round's M7 mutation (swapping the two blocks) survived the old suite and now fails this test. - Triage boundary note: pin that a bound PR's state watch events bypass the repository filter, so a future refactor of the watch call site cannot silently silence bound sessions. - Docs: soften the URL-rejection claim to "known non-repository areas" in the user guide and both design docs, and state the residual denylist risk. * fix(channels): close the ten inline review findings on the repository filter - R1-1: close the arbitrary-host class structurally. The paste-shortener now only shortens URLs on the channel's own GitHub host (github.com plus the GHES host derived from baseUrl, api. prefix stripped); api.github.com, gist.github.com, gitlab.com and any other host pass through for validation to reject. Add the missing advisories/copilot areas, a snapshot assertion over the whole reserved set (removing any member now fails, which the enumeration alone could not witness), and foreign-host fixtures. - R1-2: seed paginate before connect() in the rejection helper so a successful connect no longer prints an unasserted poll-error backoff line. - R1-3: dialog case opening on a stored active mode — the list shows the stored text and a re-save keeps both keys. - R1-4: pin the notification side of case-insensitive matching (mixed-case subject URL vs lowercase entry) and the normalizer's trim/empty handling. - R1-5: pin the complement of the watch bypass — a notification about a bound PR in a blocklisted repository is filtered, not delivered. - R1-6: capitalized reserved-root variant pins the guard's own lowercasing. - R1-7: vocabulary test binding the repoFilterMode descriptor options and default to the exported REPO_FILTER_MODES, mirroring prWatchEvents. - R1-9: two-poll journal budget reset test (42 rows, two counted rows). - R1-10: extend the Off-retirement case with the discriminating switch back to Allowlist, where a stale error would remount. - R1-11: typing a valid entry (or correcting a malformed one) now retires the shown instruction through refreshErrors, with two no-save dialog cases; Submit remains the only error population site. Mutation witnesses on this head: dropping chatId lowercasing, the host gate, the list-typing retirement branch, and the refreshErrors guard each fail exactly their new test; all restored byte-identical. * fix(channels): exclude the channel's own API host and paths from URL shortening Round-3 review findings on #16493, all closed in this commit: - R1-1 (Critical, fix-induced): the host gate re-admitted the channel's own API host — with baseUrl set, a pasted REST URL shortened to `repos/octo` (api.github.com) or `api/v3` (GHES /api/v3), phantom repositories both validators accepted. Only the WEB host now qualifies: an api.-prefixed baseUrl host contributes only its stripped form, and paths under the baseUrl's own API prefix are excluded. Genuine GHES web URLs still shorten. Pinned with baseUrl actually passed through validation, for both REST shapes; re-admitting the raw API host reddens the test. - R2-1: the baseUrl argument now has a call-site witness on both paths — a GHES draft validates clean and saves ['octo/repo'] through buildChannelUpsertRequest, mirrored in the parity table against the daemon authority. Dropping either argument reddens its half. - Deferred notes (all three closed, none deferred onward): trailing sentence punctuation is stripped before matching (a pasted `…/octo/repo.` no longer becomes the phantom `octo/repo.`); `enterprises` joins the reserved set (snapshot updated); and a dialog case witnesses the parked-list round trip — hidden while Off, restored with its text on re-enable. Mutation witnesses: re-admitting the raw API host, dropping the validator baseUrl argument, and dropping the save-path baseUrl argument each fail exactly their new test; all restored byte-identical. |
||
|
|
752e399b3b |
feat(web-shell): surface task Q&A activity above the composer (#16037)
* feat(web-shell): add a waiting minigame to the composer Show a dismissible horizontal-Tetris strip above the main composer while a prompt is streaming. The game closes when the turn ends, ignores editor keyboard targets, and stays out of split panes and coarse-pointer devices. Add engine and component tests, EN/ZH copy, design docs with page screenshots, and a Chromium E2E scenario. Refs #16036 * docs(web-shell): point the waiting minigame design at the committed E2E spec * fix(web-shell): move the waiting minigame trigger into the status line Replace the full-width waiting strip with a compact gamepad button at the end of the streaming status row. The main composer now owns the open state, and the board mounts above the status row only while open. Keep the game's window keydown listener stable through a ref so rerenders cannot drop the Escape that collapses the board, and update the component tests, E2E scenario, screenshots, and both design docs. * fix(web-shell): keep the waiting minigame scoped and recoverable Pause and resume correctly after the tab is hidden, scope game keys to the focused card so Escape no longer arms stream cancellation, let focused buttons keep Space, restore the sound toggle, and keep the board grid out of the aria-hidden wrapper that also held the overlays. * fix(web-shell): keep the waiting minigame recoverable and scoped Resume gravity after the tab becomes visible again, create the audio context when sound is enabled, let the card's own buttons keep Space/Enter, leave Escape to open dialogs, and keep the restart button out of the aria-hidden board. The main composer now lets the open game own Escape so the first press collapses it without arming stream cancel. * feat(web-shell): add a waiting card with activity, usage and games Open a waiting card from the streaming status button. The card starts on the task's 44-bucket Q&A activity heatmap, switches to token usage instead of a credits balance, and places a Qwen mark that opens a Tetris/Snake picker. Games share the 44x7 grid and offer back-to-activity and switch-game actions. Add the activity and Snake engines, component and E2E coverage, a local harness with sample data, and design docs with page screenshots. Refs #16036 * feat(web-shell): refine waiting card activity and game intros Add hover details and click-to-locate to the activity heatmap, start a random game from a delayed cold-cell Qwen entry, and play a QWEN CODE intro that flies the blocks into the Tetris start cluster. Snake keeps its intro static, eats an apple emoji, and starts at a slower speed. * feat(web-shell): show task activity in a daily calendar Replace the task-span buckets with a fixed 44-week day calendar, including month labels, per-day hover details, click-to-locate and a stable card frame across activity, usage, intro and game views. Gate the Qwen game entry behind the waiting games configuration so the activity card stays the default experience. * fix(web-shell): show a gamepad icon on the configured game entry Replace the Qwen brand mark in the calendar's game entry with a gamepad icon, remove the now-unused Qwen logo component, and let the local harness read the waiting games config so ?waitingGames=1 opens the preview with the entry visible. Game screenshots and docs now describe the gamepad mark. * fix(web-shell): read cached tokens from the connection fallback The usage card falls back to the connection's token usage before any assistant usage frame arrives, but the fallback read the message-level cachedTokens field while the connection object carries cachedReadTokens. Accept both names and pin the connection shape in the unit test. * fix(web-shell): keep the game entry hit area inside its cell Give the calendar game entry a positioning context so its forgiving hit area stays bounded when prefers-reduced-motion disables the intro animation and removes the transform that used to establish it. Cover the reduced-motion path with an E2E that hovers and clicks a day while the entry is visible. Also reuse one Intl formatter pair per locale, format task token totals above 1M as M, and drop the unused no-activity copy. --------- Co-authored-by: yungsen.hys <[email protected]> |
||
|
|
f387c3a75a |
fix(ci): recreate git metadata before the publish-triage checkout (#16494)
* docs(ci): state the real publish-triage safety argument The publish-triage header comment claimed a 'clean runner', but the job routes to the persistent ECS pool like publish-tmux and runs without a container. The guarantee that actually holds is trusted-code-only: a sparse checkout pinned to github.sha, never agent-provided code, and a publisher that imports only node builtins plus one relative helper, so a reused workspace offers no bare-specifier node_modules to hijack. Reword the comment to say that, per the non-blocking note in the #16468 re-review. Comment-only change; no job, timeout, or behavior delta. Co-authored-by: Qwen-Coder <[email protected]> * fix(ci): recreate git metadata before the publish-triage checkout The patrol closeout on the comment rewrite checked the claims against the file and found two gaps. First, publish-triage was the only job reaching actions/checkout on the shared pool without recreating the workspace .git: an earlier agent run owned that workspace and could have planted hooks/config that checkout's git commands would execute in the one job holding the write PAT. Add the same Recreate runner Git metadata step the triage/tmux/verify jobs run before their checkouts, and add publish-triage to the qwen-runner-checkout-workflow matrix that behaviorally executes it (removal witness: deleting the step turns the new entry red). Second, narrow the header: drop the 'it is safe there' certification in favor of naming the controls, and replace 'sole writer' — the triage job's reaction and lifecycle host steps also write with the PAT — with 'the only path from the agent's drafts to GitHub'. Design pair updated in both languages. Co-authored-by: Qwen-Coder <[email protected]> * docs(ci): keep the new step comment off the PAT-holder claim The publish-triage step comment and the design pair said the checkout runs in "the job that holds the write PAT", which is the same overstatement this PR removes: the triage job's reaction, status comment, and finalize step also write with that PAT. Name the publisher as what it is instead, so the header's new parenthetical and the design prose agree. Co-authored-by: Qwen-Coder <[email protected]> * fix(ci): pin the publish-triage safety claims and derive the checkout guard Addresses the remaining review findings in this PR. R1-2: publish-tmux's banner still claimed a clean GitHub-hosted runner while its runs-on selects the ECS pool; reword it to the controls that actually hold (checks out nothing, runs no repository code), routing untouched. R1-4: the publisher's bare-specifier-free import closure is load-bearing for the pool argument but nothing pinned it; add a meta-test that scans the closure and matches it against the sparse-checkout list (witness: adding import 'yaml' reddens exactly that test). R2-2: the runner-checkout guard's hand-typed job list is what missed publish-triage; derive the qwen-triage entries from pool-routed jobs that check out, so the next such job reddens with no test edit (witnesses: a new pool checkout job without the reset, and deleting publish-triage's reset, both redden). R2-3: pin publish-triage's ownership-restore fallback chain with assertUnconditional (witness: deleting the sudo fallback reddens). R2-1: the design pair's Files-affected list still said the runner-checkout test needs no change; correct both languages and narrow the pinned-by parenthetical to what each suite actually pins. Co-authored-by: Qwen-Coder <[email protected]> * test(ci): make the new pins layout-independent, floored, and complete Round 3 found three holes in the pins added by the previous commit, all in the guards themselves. R3-1: specifiersOf used a single-line pattern, so Prettier-wrapped imports were invisible in both directions — a wrapped bare specifier kept the closure test green while the pool job would die ERR_MODULE_NOT_FOUND, and a wrapped relative one dropped a needed entry from the expected sparse list. Span newlines, hoist the scanner to module scope, and fixture-pin it (wrapped relative, wrapped bare, and the publisher's own multi-line node:fs import); those three fixtures were verified red against the old pattern and green after, and the sparse comparison stays exact equality. R3-2: the derived job set had no floor, so rewriting one pool job's runs-on into a vars lookup silently dropped its row; add an arrayContaining floor that names the lost job while still admitting new pool jobs with no test edit. R3-3: the ownership test's name promised the full fallback chain but assertUnconditional matches chmod unanchored, so deleting the sudo chmod arm stayed green; pin that arm in this test rather than the shared helper, which has five other call sites. Witnesses: wrapped bare import reddens the closure test, a vars-lookup runs-on reddens the floor naming publish-triage, and deleting the sudo chmod arm reddens the ownership pin — each with byte-level restore. Co-authored-by: Qwen-Coder <[email protected]> * test(ci): prove the sparse checkout by loading it, not by scanning syntax The APPROVED review deferred three findings against the closure guard, all instances of one root cause: the guard modelled JS module syntax with a regex. Proven on the shipped code — a 1164-character wrapped bare import left it green (the 400-char bound), and an out-of-tree helper turned the byte-offset slice into the garbage entry /.github/scripts/elper.mjs. Replace the scanner with the condition the pool actually creates: materialize exactly the sparse-checkout list into a temp tree, assert no node_modules exists above it, and import the publisher there. Sufficiency is the load succeeding; necessity is each .mjs entry omitted making it fail; the owner policy is tied by resolving the publisher's own new URL literal and requiring that exact path in the list, instead of an unanchored substring plus a hard-coded entry. This removes the scanner and its layout fixtures rather than patching them again — wrapped imports of any length, prose that looks like an import, and out-of-tree helpers are all covered by the load itself. Witnesses: the long wrapped bare import that was previously green now fails, as do a short bare import, an out-of-tree helper, a helper dropped from the list, a stale extra entry, and a moved owner-policy path; each with byte-level restore. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
e99286813e |
fix(ci): preserve Maven suite results across cache write-back failures (#15274)
* fix(ci): scope the sdk-java test lock to the cache write-back (#15262) The Managed Agent fast tier can fail without reporting a test result after waiting out the per-host SDK Java lock. #15128 scoped the MariaDB lane's lock to its cache write-back, but the test job still held the same lock across the SDK and Managed Agent Maven suites, so a long full-tier suite could starve the fast tier on that host. Seed the test job's Maven repository from the host cache, run both suites unlocked from that job-local copy, and take the ticketed host lock only for the cache write-back. The write-back children close fd 9 so a cancelled job's survivor cannot retain the lock. Pin the new order and the write-back fd closures in the workflow tests, and update the fair-lock design docs and the workflow size baseline. Fixes #15262 Co-authored-by: Qwen-Coder <[email protected]> * fix(ci): keep sdk-java cache write-back best-effort (#15262) * fix(ci): keep the sdk-java gate result across the cache write-back (#15262) The write-back runs after the suite, so the step's exit code must come from the suite: both lanes now track the Maven status, demote only a lock-wait timeout with a step-summary record, and redden when the queue itself is unusable. The wait drops to 1800 s because it guards only the bounded copy; the seed stops hiding cp's diagnosis while staying quiet on a fresh host; the design docs record the head-of-queue stall the shorter hold does not bound. * fix(ci): revert the sdk-java lock helper to the PR footprint (#15262) The previous round changed .github/scripts/sdk-java-lock.sh, which this PR never touched; the deterministic gate rejects that expansion. Restore the helper and keep both call sites on the pre-round `if !` shape, retaining the 1800 s wait and the step-summary record. Drop the status-2 pins and tests that only existed for the reverted helper, and record the unusable-queue gap as a follow-up in the design docs. The timeout-returns test stays. Co-authored-by: Qwen-Coder <[email protected]> * fix(ci): de-alias every resolver tracking file and guard an unusable lock queue (#15262) * fix(ci): de-alias Maven install metadata in the sdk-java seed (#15262) Co-authored-by: Qwen-Coder <[email protected]> * test(ci): restore the per-job Maven seed binding (#15262) * fix(ci): keep sdk-java resolution metadata out of the host cache (#15262) The seed drops maven-metadata-*.xml hardlinks, but the write-back still published job-local metadata with cp -n, where the first publisher's copy could never be refreshed. Exclude it at both write-back sites and pin the seed and write-back name sets to each other. Also witness each suite-status exit with an exit-1 case, restore the lock test's combined-stream assertion, and bring the design docs' acceptance criteria and fault arms up to date. * test(ci): carry the deferred-status helper into the sdk-java slices The tolerated-skip arms now call mvn_report_failed_suite, which the step defines above the lock block. Every slice that starts at lock_setup_fault() or at the write-back therefore ran bash -e without the definition and died with 127, which read as a code failure rather than a missing prelude. * docs(ci): clarify remaining sdk-java cache hardening scope * fix(ci): bound failed MySQL cache waits and exclude resolver residue Co-authored-by: Qwen-Coder <[email protected]> * fix(ci): clear inherited resolver state in the Maven cache seed prune The seed de-alias prunes carried -links +1, which restricted deletion to the hardlinks cp -aln created in this job. RUNNER_TEMP survives across jobs on the pool, so a previous job's mutable resolver state (nlink 1 *.lastUpdated, _remote.repositories, resolver-status.properties) survived in the job-local repository and could redden an otherwise green Maven lane. Drop -links +1 from all four seed prunes so inherited state is cleared again; the write-back's -links 1 delta filter is unchanged and the seed stays best-effort (2>/dev/null || true). Move the deAlias test pin with the fix and add a regression witness that plants single-link markers in the job-local repository before the seed body runs and asserts they are gone (red with -links +1 restored, exit 1; green with the fix, on macOS and Linux). Re-record the sdk-java.yml .size-baseline entry (153970 bytes) and synchronize both design-doc languages, including the nine-coordinate set the main merge brought in. --------- Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: Qwen Autofix <[email protected]> Co-authored-by: qwen-code-dev-bot <[email protected]> Co-authored-by: Shaojin Wen <[email protected]> Co-authored-by: yiliang114 <[email protected]> Co-authored-by: Shaojin Wen <[email protected]> |
||
|
|
3f99e022b7 |
feat(cli): have an Ink TUI turn send the round its tool batch continues with (#16428)
With experimental.unifiedAgentLoop on, the shared tool batch's completion step hands the next round to the turn, which takes the input queued for the tool boundary and sends it. The batch keeps the scheduler and the TUI's activity until then; a turn that does not wait leaves the round to the batch. The Ink parity suite adds retry and queued-input scenarios. Part of #15327. |
||
|
|
11c83aa21e |
feat(managed-agent): allow concurrent child questions (#16484)
Release local execution membership while questions wait, recover the original runtime owner, and isolate root-page action selection and drafts. Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
e98e4db75e |
perf(managed-agent): cache parsed statements on the MySQL pools by default (#16414)
Connector/J parses a statement's SQL on every prepareStatement unless its per-connection parse cache is on, and that parse was the largest share of the control plane's Java CPU under load. The server and embedded MySQL pools now add cachePrepStmts=true, prepStmtCacheSize=512 and prepStmtCacheSqlLimit=8192 as connection properties. A connection property overrides the URL in Connector/J, so each setting is added only when neither the URL nor the Druid connection properties name it, ignoring case, percent-encoding and surrounding spaces. Nothing is added when either names useServerPrepStmts or useCursorFetch, since the cache would then hold statements open on the server. The server pool binds spring.datasource.druid inside its factory, before the defaults are added: @ConfigurationProperties bound it only after the factory returned, and the string form of connection-properties replaces every connection property. |
||
|
|
acea5b1335 |
docs(design): correct the daemon PR state transitions design (#16487)
Fold the unposted findings of #16246's final docs-only review, and what the audits of those fixes turned up, into both language versions: - v0.26.8 (2026-11-05) ships the watch:* keys of #16258, so they follow the same deprecation window as prWatchEvents. - The view identity check normalises both URLs as pullRequestKey does (any path after the number and the port ignored) and compares host and number only. - Latency is stated as nominal (one interval, or ceil(n/30) for a view set of n), with the cases that make it longer. - The shared template contract moves key and template validation, chain resolution (event vocabulary and comment lane as parameters) and rendering from variable values into channel-base; %sender% is GitHub, %repo%/%url% keep the winner's bound URL spelling. - Every watch-file change reads and writes within one hold of the per-PR lock; a daemon claims the watch only when its refresh timer runs and its uid owns the watch directory and files, so writes stay temp-and-rename; a non-owner writes nothing. - The alias guarantees, the untrusted-primary exposure and the validation plan are updated to match; the Chinese twin carries the technical templates verbatim. |
||
|
|
301b61ddcc |
fix(channels): deliver plain conversation comments to PR-bound sessions (#16416)
* fix(channels): deliver plain conversation comments to PR-bound sessions With deliverToBoundSessions on, a plain comment on a bound PR still had to pass the repository's mention requirement before it reached the session. On threads GitHub lists as a mention, an assignment or a review request, the comment lane dropped it whatever the setting. On subscribed and similar threads the bound path's preflight rejected it, and the workaround, turning requireMention off, also made the channel answer plain comments on unbound PRs and issues there in public. Threads the bot account opened or commented on were not affected. The mention requirement keeps ambient chatter out of public replies; it is no authorization boundary. The private path now waives it through a preflight option that admits a message whose only group rejection is the mention requirement. The waiver covers a comment, not the PR's own first-contact description, from a sender the repository's allowlist names, in a repository the group gate admits, unless the sender is in operators: a bound session usually acts on GitHub as an operator's account, and its own comments would come back to it as turns. Sender, repository, pairing and message-route checks still run, and repositories that admit any sender keep the requirement. On the mention-type threads the lane admits such a comment only when the private path would deliver it: the routes match and a target resolves for it as a comment event, so templates keyed by the thread's reason do not apply to it. Its task is marked private-only: it never takes the channel path or posts a public error comment, and it is retired when no bound session is left. Comments only the waiver admits are dispatched after the rest of the lane and the first-contact body, so their outcome, a deferral or a gone session evicted from the binding map, cannot change how those are handled. Two failure paths that predate this change no longer post a public error comment for a bound event when the task store fails: the gone-session deferral under a partial scan and the retirement at the deferral cap. * test(channels): pin the default route, audit refusal and window hold of waived comments - Admit an unprefixed plain comment through `defaultMessageRoute`. - Keep a waived comment's key out of the cursor when an unreadable publication audit refuses its dispatch, and deliver it once the audit reads again. - Feed the first-contact body on the re-read when a failed waived delivery holds the notification window. |
||
|
|
dd4515ca27 |
feat(ci): isolate triage agent credentials and run all triage on ECS (#16468)
* feat(ci): isolate triage agent credentials and run all triage on ECS The triage agent carried a write PAT, so comment reruns and external fork-PR triage had to stay on ephemeral hosted runners while hosted capacity is tight and the ECS pool has headroom. Wire the token-isolation follow-up the workflow already documented: the agent reads GitHub with the job's read-only GITHUB_TOKEN and expresses every write as a draft; a separate publish job validates and executes drafts with the bot PAT, using one publisher script shared with local /triage runs. Contain the agent job, launch the CLI directly behind the hardened loopback model proxy, and move the deterministic maintainer resolver into the publisher. With no write credential in the agent environment, route every triage trigger to ECS behind the existing kill switch, and retire author_can_write. Markers, bot identity, and the finalize workflow's approve-on-green protocol are unchanged. Co-authored-by: Qwen-Coder <[email protected]> * fix(ci): bump triage CI budget and address triage review findings The new publish-triage job crossed the .ci-budget ratchet (7->8 jobs, 280->290 declared timeout-minutes), aborting Lint & Static before any lint or helper test ran. Bump the budget entry, mirror the artifact-name fallback expression on the upload side so an early-death run's drafts stay addressable, and enforce SKILL.md's process-label rule at the publisher chokepoint (welcome-pr, maintainer, help wanted, good first issue) with fail-closed tests, aligning the issue-workflow instruction that previously contradicted the rule. Co-authored-by: Qwen-Coder <[email protected]> * fix(ci): harden the triage draft pipeline after an adversarial audit Six findings from a multi-round pre-push audit, each with a mutation witness: kill lingering agent-user processes before the drafts strip/upload so a detached child cannot re-plant symlinks in the race window (upload-artifact follows them and RUNNER_TEMP is host-shared; unkillable survivors drop the draft set deny-by-default); lstat the draft containers themselves in the publisher preflight, since a symlinked bodies/ redirects the realpath containment base; reject cross-kind actions (review/close-pr on issues, close-issue on PRs); install util-linux for runuser as the sibling lanes do; make the mktemp'd proxy-wrapper directory traversable by the agent user so the gh/git wrappers do not silently fall out of its PATH; pin the artifact-name symmetry, the kill-before-strip ordering, and the toolchain install line. Design pair updated in both languages. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
1ea05364a6 |
perf(managed-agent): take tool execution admission off tenant placement (#16478)
* docs(managed-agent): design tool execution admission without tenant placement Admission still takes tenant placement on every tool call. It orders admission's plain read of the harness drain fence against requestHarnessDrain, the only fence writer that holds no row admission takes. Lifecycle phases already hold the Session root, and the storage fence is backstopped by migration's retirement check. The design has the drain take the Session root after placement, and lets an ordinary session-class admission with an existing root skip placement, taking the root as its first statement so a REPEATABLE READ snapshot cannot predate the drain. Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(managed-agent): record the storage alias order blocker for placement-free admission Co-Authored-By: Claude Opus 5.5 <[email protected]> * perf(managed-agent): take tool execution admission off tenant placement Every tool call still took tenant placement in admitExecution. Placement ordered admission's plain read of the Harness drain fence against requestHarnessDrain, the only fence writer holding no row admission takes. requestHarnessDrain now locks the Session root after placement. An ordinary session-class admission with an existing root, no lifecycle authority and no storage or LOCAL storage skips placement and locks that root as its first statement, so its REPEATABLE READ snapshot cannot predate a committed drain. Other admissions take placement first, as before. completeStoppedSessionRelease locked the storage alias and owner rows before the binding, the reverse of admission and createSession. It now locks the binding first, so a placement-free admission cannot close a cycle with it. Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(managed-agent): format the admission design with the pinned Prettier Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
06a0cb3250 |
perf(managed-agent): take runtime binding control off the tenant guards (#16480)
Binding control took tenant placement and the publication guard for every session-class binding, so each periodic lease renewal queued on both tenant-wide rows. The publication guard only tells whether a CSI profile original's retention root exists; any other binding is answered null or refused whatever it says, so lockBindingControl now takes it for profile bindings only. Claim, renew and release change only the operation lease and move no binding out of its state. Like dispatch, they now decide the CSI pin under the Session root alone when it exists. compareAndSet keeps placement, which it takes next and which must precede the root. Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
45cde86998 |
feat(cli): align managed agent basic tools with daemon (#16445)
Expose glob in ordinary file sessions, durable todos in Shell sessions, and PDF page selection in Hosted read_file. Restore saved todo state on session reload while keeping exact current-schema validation and restricted resident/CSI tools. Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
7cb46d9fed |
feat(hooks): add FileChanged notifications for local sessions (#13969)
* feat(hooks): add FileChanged notifications for local sessions * fix(ci): preserve shell gate coverage and trim transcript data * fix(ci): flush debug prompts and isolate git fixtures * fix(hooks): respect ACP watcher ownership and skill registration * test: stabilize hosted recovery and failed spawn fixtures * fix(ci): budget the complete hosted integration fork * fix(runtime-broker): accept MariaDB CSI guard metadata * test: align schema baseline and hook dispatch waits * fix(ci): allow hosted shared fork to finish all classes * fix(hooks): preserve file watching across errors and cwd changes * fix(ci): isolate reference archive extraction per run * test(ci): keep reference archive checks dependency-free * fix(hooks): constrain recursive watches and repair CI * test(web-shell): synchronize verified connection return * fix(ci): correct runtime broker callback indentation * test(web-shell): match the managed workspace registration label Keep the remote workspace registration smoke checks in sync with the service label added by unified workspace navigation. Preserve exact text matching and all registration and replay assertions. * fix(ci): reduce standalone runtime fixture overhead * fix(hooks): ignore implicit atomic-write temporary files * test(managed-agent): recover initial node port conflicts * fix(hooks): observe parents before discovery anchors disappear * test(managed-agent): await stream cleanup before recovery teardown * test(managed-agent): align hosted shell fixture capabilities * test(managed-agent): let OS choose initial harness port * fix(ci): restore runner-local Java cache permissions * test(runtime-broker): use native execution profile paths * test(managed-agent): bind valid workspace upload probes * test(managed-agent): reuse the upload probe digest fixture * test(managed-agent): bind hook fault listeners to available ports * test(managed-agent): align the native profile path fixture * test(managed-agent): Wait for projection refusal retries * ci: Prepare daemon Java cache before Node preflight * test(managed-agent): check channel model calls after delivery * fix(hooks): observe explicitly registered symlink targets * test(managed-agent): separate consumption from relay snapshots * test(managed-agent): target tamper injection by resource key * test(managed-agent): exhaust attestation EOF retry in fault gates * fix(ci): bound RocketMQ release downloads * test(tui): await the restored assistant before capturing history * fix(ci): use a checksum-pinned RocketMQ mirror * fix(ci): provision missing browser test fonts * fix(ci): quote the font probe environment * fix(ci): prepare browser fonts without elevation * test(core): compare complete prune reports without ordering * fix(ci): repair only runner-local Java tool caches * test(managed-agent): qualify fixture JDBC templates * fix: isolate reconnect hooks and cancellation test fixtures Skip chat initialization so MCP reconnect cannot re-enable file hooks, and keep cancellation fixtures isolated from the background worktree scanner. * fix(ci): tolerate a missing Java cache helper on resumed releases Resuming an existing release checks out a tag that may predate the helper, which failed all five Java jobs before setup-java ran. * fix: finish the main merge for harness deadlines and workflow size Use the renamed settle-timeout accessor in the merged hosted-harness tests, and record sdk-java.yml's new size after the resumed-release cache guard. * style(managed-agent): restore import grouping in merged verification tests * test(managed-agent): require consumed child result mirrors * test(web-shell): center repository settings capture --------- Co-authored-by: yiliang114 <[email protected]> Co-authored-by: yungsen.hys <[email protected]> Co-authored-by: Shaojin Wen <[email protected]> |
||
|
|
1853b1505f |
fix(subscription): isolate activation and restore sign-in recovery (#16453)
* fix(web-shell): wait for session before checking subscription commands * fix(cli): keep signed-out subscription startup in the composer * fix(subscription): preserve model selection across sign-in * fix(subscription): align sign-in with model selection scope * fix(subscription): apply reasoning defaults across models * fix(subscription): tag Qwen web links by client * fix(subscription): prefix Qwen web client tags * fix(subscription): attribute Qwen web links by paywall scene * fix(subscription): recover historical sessions with saved API models * fix(cli): remove the signed-out subscription startup notice * fix(subscription): support standalone desktop activation and expose errors * fix(subscription): isolate activation and session model selection * fix(subscription): enforce auth policy and restore home login * test(cli): preserve the default initializer mock on restore --------- Co-authored-by: luoyun <[email protected]> Co-authored-by: 易良 <[email protected]> |
||
|
|
1b1f4455d9 |
ci: build the PR tree once and shorten the required path (#16450)
* ci: build the PR tree once and shorten the required path The no-AK integration gate, the four web-shell smoke shards and Lint & Static each rebuilt the tree test_build already uploads, and the build cache rarely helped: most runs logged "29 of 29 workspaces changed" because the arch-keyed cache was split across the mixed x64/ARM64 pool and a prefix restore returned the newest entry instead of the base commit's. - test_build restores the build its base commit saved (ci-build-cache.mjs key --at), drops runner.arch from the key, and runs the serve fast-path closure and core subpath checks against the built tree. - The no-AK integration gate unpacks that tree and runs as three shards; integration_no_ak_result keeps the required check name. - The web-shell smoke unpacks the same tree instead of building; consumers recopy the opentui native assets for their host (host-assets). - The standalone runtime regression moves from unit shard 1 to shard 4. - A java_only profile skips the TypeScript lanes for changes confined to the Java packages; Lint & Static runs the scripts/tests suites that name them. - Drafts skip the integration gate and the browser smoke; ready_for_review starts the full run. * ci: check the settings schema in test_build, which has the core dist Lint & Static only had core's dist because the serve fast-path check built it; with that check moved, Generate settings schema failed to import it. * ci: keep the standalone regression on unit shard 1 Moving it did not shorten shard 1: the regression runs in the side lane, in parallel with the CLI and core suites, and shard 1's time is its CLI slice (576 s against 147 s on shard 4 for the same 181 files). That imbalance is left to duration-based sharding. |
||
|
|
1d64177286 |
docs(providers): document OCI Generative AI under OpenAI-compatible providers (#16345)
* docs(providers): document OCI Generative AI under OpenAI-compatible providers Adds a subsection to model-providers.md covering OCI Generative AI's two OpenAI-compatible base URLs (Chat Completions and Responses), Bearer API-key auth, the publisher-prefixed model ids, a ready-to-paste settings.json, and the endpoint behaviours a user otherwise has to discover by trial: pinned contextWindowSize, no model discovery, the max_completion_tokens requirement of GPT-5 / o-series on Chat Completions, the Responses-wire requirement for gpt-5.6 / GPT-6 tool calling, gpt-oss staying on Chat Completions, compressed error bodies, and imported models addressed by endpoint OCID. Refs #16332 (docs-first path recommended in triage). * docs(providers): list the API-key regions for OCI Generative AI The OCI section named `uk-london-1` among the regions that serve the OpenAI-compatible endpoints and omitted Hyderabad and Osaka. The API Keys page is the source of truth for the Bearer-key path this section documents, and it lists Frankfurt, Hyderabad, Osaka, Ashburn, Chicago and Phoenix; London is only on the IAM-auth agentic-inference list. Name those six regions and link the API Keys page instead of the agentic regions page, since it also lists the model families the key covers. |
||
|
|
e777fc7502 |
feat(managed-agent): enable automation by default (#16448)
* feat(managed-agent): enable automation by default Co-authored-by: Qwen-Coder <[email protected]> * style(docs): match pinned Prettier formatting Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
a051a6a796 |
fix(web-shell): batch session deletions at the route limit (#14637)
* fix(web-shell): batch session deletions at the route limit * test(web-shell): cover bulk delete in visual previews * fix(web-shell): retain bulk delete interruption errors * fix(web-shell): retain deletion failures across workspace batches Collect per-session and request failures throughout a deletion without losing later causes or earlier successful current-session cleanup. Keep archive behavior unchanged and document the daemon request-limit coupling. Add gated regressions for both cross-workspace failure orders and multiple per-session errors across batches. The new gates reject parallel-workspace and per-workspace-error-reset mutations. * test(web-shell): cover bulk delete merge boundaries Preserve workspace and standalone failure causes for per-session and rejected requests. Verify each deletion batch captures its UI owner before the request and invokes its own invalidation callback with that batch result. --------- Co-authored-by: qwen-code-dev-bot <[email protected]> |
||
|
|
ff775787f5 |
feat(channels): journal private bound-session delivery and scan health (#16237)
* feat(channels): journal private bound-session delivery and scan health The event timeline could not tell the private delivery lane's story. A prompt injected into a PR-bound session ended on the same `running` word as an event still in flight, no task row named the session it reached, and the three private watch retires plus the dead-letter left only stderr behind — so a silent consumption looked like a quiet repository. A bound event rejected by the channel's own gates was worse than invisible: its skip row was followed by running rows, reading as a delivery. The private delivery method returned a boolean meaning "settled", which conflated an injected prompt, a private retire and the public fallback. It now returns a verdict, and the task runner derives the journal's terminal word from it, so an event that was never delivered can no longer be recorded as delivered. Routing, fallback, retry-budget and cursor decisions are unchanged. The binding scan is a new hard dependency: while it has never succeeded the cold-start gate stalls every notification, yet the poll reports ok. Degraded rows now surface that beside the stderr lines the adapter already throttles, and on the edge of the existing watch-reconcile flag, so a permanent outage cannot flood the journal. Attaching this to the poll detail was rejected: both cold-start gates return before the detail is set, which is exactly when it would matter. No new event type and no protocol change — an outcome is already an open string. The dead-letter's message body and its failure-path payload dump stay out of the journal. Also labels `failed_ambiguous`, which was already written but rendered as a raw token. * fix(channels): make the bound-delivery journal claims true Review round 1 found sixteen places where the journal, its labels or its documentation promised something the code did not do. All sixteen are addressed here; none is deferred. Three were ordering or attribution defects in the new rows. A watch retire was journaled before the removal it describes, so a throwing removal left a terminal claim for an event the next poll re-runs and retires again under a different reason. Rows written while a delivery is cleared lost the session they were about, which is the one fact the private lane's attribution adds. And the pre-claim watch retire sliced the loaded state before saving with no rollback, so a failed save lost the event with no trace at all — the comment claimed the opposite. Two were counts that measured something other than their label: the partial scan published the routing map's size, which counts bound PRs, as a session count; and the streak advances per binding scan, not per poll, because a gone-session retry refreshes again inside the same poll. The session count now comes from the scan's own rows, and the streak is labelled as scans in both languages and in the stderr lines that share the counter. Re-cadencing the counter itself would change the throttle timing the previous PR shipped, so the label moved instead. Two were missing surfaces: the pending-queue-overflow retire wrote only stderr and the dead-letter file, and an unreadable watch state was reported as a pending backlog whose stated remedy cannot clear it. The overflow now journals a body-free skip, and the watch guard classifies a read failure separately while its gate stays fail-closed. The recovery signal the prose promised did not exist: a poll/ok row is written after every poll that does not throw, including all of an outage, so it cannot mark the end of one. The watch guard now journals its clearing edge, and the prose says why ok rows cannot serve. The rest were documentation and test-coverage corrections: the protocol reference promised detail.streak on every degraded row when only the binding-scan rows carry it; the design file map certified the protocol reference as unchanged while this branch edits it; the override's stated consequence was backwards; and five guards had no witness or only half of one. Each now has a test, and the suite grew from 438 to 444 in the GitHub package. Verified by 37 mutations, all killed. Two initially survived and were investigated rather than accepted: a loose sessionId count that three unattributed-row-free rows still satisfied, and a filter that matched no test after a rename. Both were tightened until the mutation died. * docs(channels): align the journal ledger with the round-2 verification The validation section still cited the round-1 mutation count after the review fixes added fifteen more, and the Chinese half had lost the count altogether during the rewrite. Both now state 37 applied and 37 killed. Also records why two mutations initially survived and what was done about them, since a sweep that cannot distinguish "no test ran" from "the guard held" is not evidence: a loose sessionId count that three correctly attributed rows still satisfied, and a filter left matching nothing by a test rename. The Web Shell line now mentions the absence coverage too. * fix(channels): keep the lost bound session named on every journal row The second review round of the bound-session journal found fifteen gaps. Three were consequential, and two of those were introduced by this PR's own previous round: The gone-session retry never updated the local delivery, so with two followers both gone the terminal row named the first one — a session evicted a step earlier — while the row above it named the last. An operator reading "which session lost this event" would open the wrong workspace. Remembering the target only for the transition that clears the delivery is not enough: once the clear is persisted, every later poll re-enters the absence gate with no delivery to read, and the dead-letter at the deferral horizon reads a record that has none either. A driven run of that sequence left 239 of 242 rows unattributed, with the terminal row naming nobody — the one fact this lane's attribution exists to supply. The lost target is now persisted as attribution only; the delivery is deliberately not restored, since that would re-enter bound delivery and change routing. The overflow retire was journaled before its queue shift was durable, with no rollback, so a failed save left a skip row for an event a restart would later deliver. It now saves before it journals and restores the shift when the save throws, matching the sibling pre-claim path. Also: the partial-scan session count now includes the sessions retention keeps routing (a draining workspace lists none while still delivering to three), and both pre-claim retires carry the frozen target so the timeline and the dead-letter agree about the same event. Four coverage gaps whose mutants left the whole suite green are closed: the guard keeping the partial-scan row off the cold-start path, the session on the no-candidate retire, a second healthy poll so the recovery row's edge trigger is distinguishable from an unconditional write, and both a shape test and a Chinese render test for the interpolating labels. The docs said more or less than the code: detail.sessions is carried by one row, not by every partial scan; the normative sessionId definition read a retire chip as a delivery; the emit-point inventory missed the overflow site; and bindings_partial was described as recovering implicitly when a successful multi-page scan is its producer. Auditing that last correction found the stderr census was still incomplete — the lane has five recurring health and capacity sites, not three — so it now names all five, ranks them by consequence and states its own scope. * docs(channels): define what the partial-scan session count actually counts The count became listed-plus-retained when retention was included, but three surfaces still described the old value. The protocol reference called it "the sessions the map still routes to", which is wider than the code: the map also skips channel-owned sessions and unparseable binding URLs. Both design halves still said it was "the scan's own row count", which is narrower than the code and was the exact wording the review asked to correct. Define it as a session-row count — the rows this scan listed plus the sessions retention keeps routing, bounded by the retention cap — and say explicitly that it is not the number of entries surviving the map's own filters. Also record that "listed" in the row label and in the stderr line is shorthand for listed-or-retained and that both print the same number, so the wording cannot quietly drift apart from the value again. Documentation only: the adapter and its tests are byte-identical to the previous commit, so every runtime result reported for this branch still applies. * fix(channels): close the review gaps in the private-lane journal Address the eight open suggestions and the five deferred findings from the round-3 review, each with a mutation-verified guard (10 new mutations, 10 killed; one cap-count fixture initially survived because the first empty partial already capped lastBindings, and was rebuilt so retention is above the cap exactly at the emitting scan). - R1-15: the overflow dead-letter append and the sidecar save now fail independently. A failed save rolls the shift back, names the watch-state path instead of blaming the dead-letter file, drops the payload dump (the body is already durable), and remembers the head so the next detection skips the re-append. The save-throws test now drives a second detection and asserts exactly one dead-letter line. - R2-6: the pre-claim skip comment and both design languages now say only the overflow retire writes a dead-letter record; watch_no_candidate and no_role_matched leave the skip row as the event's only trace. - R2-3: bindings_cold_empty no longer claims implicit recovery — an empty partial is a successful scan, the streak resets only on a complete or non-empty snapshot, and past the escape the silence is the throttle maxing out, not recovery. - R3-1: a 1100-session fixture pins detail.sessions to the 1024 retention cap at the emitting scan; swapping retainedCapped.length for retained.length reds it while the two sibling count tests stay green. - R3-5/R3-6/R3-15: the design's outcome cell lists failed_ambiguous and degraded, the watch-guard bullet admits the read-failure error field, and the sessionId carve-out covers every row written on a clear plus the pre-claim skip; the chip sentence says reached-or-lost. Both languages. - R3-7: the three private-retire watch tests assert the public path stayed empty; leaking the envelope through handleInbound reds them. - Deferred: the gone-horizon attributes the in-memory task before retiring and the dead-letter record names the lost session; the watch-guard row re-journals when the blocking cause changes; the subscriptions-off recovery edge has a test; the recovery row renders Recovered/已恢复 instead of Degraded (wire outcome unchanged); a gone-retry fixture pins the scan-scoped streak at 10 inside poll 4. Verified: channels/github 595 passed, web-shell channels 343 passed, eslint and prettier clean; design mutation ledger updated to 47/47. * fix(channels): rework the overflow retire ordering and close round-4 review Critical R4-1: the overflow branch saved the sidecar mid-pass, committing the advanced baseline before the pass's later events were queued; a crash then never re-derived them. Remove the mid-pass save entirely — the head is sliced in memory only, and the baseline and full queue commit together in the single end-of-pass save, with the skip rows journalled after it. The dead-letter record is now the retire's commit point. A lazily loaded, file-backed key set (restart-durable, replacing the process-local map) skips the re-append (R4-16), and both drains recognize a dead-lettered head and complete its retire instead of delivering it, so no event is both delivered and filed as retired undelivered (R1-15 follow-up). Also addressed: - R4-12/13/14: terminal, transition lost-session and pre-claim rows carry sessionWorkspaceCwd so Open-session reaches a session in another workspace. - R4-11: the no_role_matched comment states the real ordering — the dedupe cursor is in memory until the base saves it, so the skip row is at-least-once. - R4-15: already_delivered_other_role is documented as a defensive, unreachable guard and dropped from the published vocabulary. - R4-2/3, R3-5: §1 outcome cell adds suppressed/posting, reason covers task and poll rows, sessionId carve-out adds cancelled and distinguishes live running rows. Both languages. - R4-4/5/6: the pre-claim census acknowledges the role-aware drain's pre-existing fourth retire; the retire-carries-session rationale notes the preflight exception; the dead-letter write-failure note names the non-recoverable public-suppression exception. Both languages. - R2-3/R4-7: cold-empty reset is keyed on the routing map and trust flag, and the escape's lane-open claim is qualified for the refresh-failure producer. - R4-8/9: the recurring-site census adds the credential/rate-limit pass abort and corrects the no-existing-flag claim for the stale-map refresh failure. - R4-10: watch-guard error wording covers reconciliation failure too. - R4-17/18/19: openTarget joins the interpolation guard with a ZH render assertion, the EN streak label says consecutive, and ZH degraded/retired/ recovered labels get render assertions. Verified: channels/github 601 passed, web-shell channels 331 passed, eslint and prettier clean; 4 new mutations killed (mid-pass save, file-dedupe, drain guard, workspace lookup); design ledger updated to 51/51. --------- Co-authored-by: verify-round <verify-round@local> Co-authored-by: verify-pr <verify@local> |
||
|
|
a1d713e747 |
feat(cli): report interactive program status with OSC 7501 (#16407)
* feat(cli): report interactive program status with OSC 7501 Report a single root OSC 7501 record from the interactive UI so terminals and agent inboxes can show whether Qwen Code is working, waiting on the user, or finished, without parsing the screen. - idle / working / blocked (permission, question, auth) / done / error, with app=qwen-code on every report - done and error latch until the next turn; a cancelled turn returns to idle; done and error are kept on exit, everything else is cleared - msg names the tool at most, never its arguments or model text - TTY only, deduplicated, retried after a failed write; never wrapped for tmux passthrough - cleared while an external editor owns the terminal, rewritten on return and on SIGCONT Closes #13870 * fix(cli): latch the OSC 7501 turn outcome when the turn ends The outcome was recomputed from a history index on every history change, so an error added while idle (a failed slash command) flipped `done` to `error`, and memory compaction reset it to `idle` or hid an error from the turn. Decide it once when streaming state returns to Idle, bound the turn by item id, and reset only when history is cleared. Also count a blocked prompt and a stop-hook loop as failed turns, and pin the msg-only report, release/restore and editor call sites with tests. * fix(cli): reset the OSC 7501 outcome on /clear and cap msg by decoded size /clear only masked the latched outcome: the tracker kept `done` or `error`, so the next item added while idle brought it back, and exit could keep a stale badge. Reset the tracker when history empties. The msg limit checked only the encoded length, which lets a 2049-byte message through as 2732 base64 characters. Cap the decoded text at 2048 bytes, which also bounds the encoded form. Correct the design doc: a slash command runs as a turn and decides its own outcome; it is not an idle history change. * test(cli): assert the decoded msg is present before measuring it decodeMsg returns string | null, which tsc --build rejects as a Buffer.byteLength argument. * fix(cli): ignore errors already pending when an OSC 7501 turn starts A terminal API error stays in the pending area until the next submit commits it, and a Goal submission skips that commit. The next Goal turn then ended with the old error still pending and latched `error`, which exit kept. Record the failure items pending at turn start and count only new ones. Also restore the program status before the raw-mode restore in useLaunchEditor, which can throw; say control characters become spaces in the msg encoder; and note in the design that the OpenTUI renderer does not report status yet. |
||
|
|
32d22f950c |
feat(managed-agent): support durable structured user questions (#16433)
Connect Hosted question actions, structured answers, durable continuation, and the WebShell question form. Cover canonical response replay and pending-question recovery across Harness and Java context restarts. Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
1a20792cfa |
ci: route the no-AK integration gate to ECS for every pull request (#16132)
* ci: route the no-AK integration gate to ECS for every pull request
Same trust class as lint_and_static (2026-11-02 extension of the
2026-10-31 decision): the integration step executes PR code, but the
lane carries zero secrets by construction — every model key is emptied
at the step and the job's only token is the read-only checkout grant.
The gate's step-level runner.temp HOME becomes run-unique and is
cleared before use (the R2-1 lesson: RUNNER_TEMP survives across jobs
on this pool). test and integration_cli keep the association-gated
ubuntu_runner. Mutation-verified: runs-on revert, fixed HOME, and a
real key in the PR-code step each red the routing battery.
* fix(ci): isolate no-AK HOME from the first step; value-pinned zero-secrets
Round-2 review findings: (1) Critical — the gate-scoped HOME missed the
lane's actual fork-code start: checkout's local action and the install
(lifecycle scripts plus scripts/prepare.js, un-skipped) ran under the
runner's real shared home with a shared COREPACK_HOME — the exact
cross-job poisoning channel the lint acceptance closed. Mirror lint:
first-step GITHUB_ENV run-unique HOME + COREPACK_HOME inside it +
shared verified XDG cache + always() reclamation. (2) Critical — the
zero-secrets guard was name-based and missed odd-named secrets; enforce
by VALUE (every non-path env key empty, no secrets.* anywhere in the
gate). (3) runs-on literal pin tightened (timeout line also mentions
lint_runner). Docs: EN single-lane phrasing, stale merge-queue claim,
2026-11-02 paragraph rewritten for the first-step shape. Six mutations
red (first-step removal, shared corepack, odd-name secret, no cleanup,
runs-on revert, fixed HOME).
* docs(ci): correct the enforcement-order claim and stale trust-basis note
Round-2 review: the doc claimed the routing battery runs before
dependency install — false (HELPER_TESTS imports yaml, runs after
install, and not at all on github_ci_only PRs' own runs); and the
ci.yml trust-basis comment still described the round-1 gate-scoped
HOME shape. State the actual timing and detection latency, and the
shipped first-step shape.
* fix(ci): scrub ANTHROPIC_AUTH_TOKEN at the no-AK gate; honest cache naming
Round-3 review: the Anthropic SDK's auth resolver back-fills
ANTHROPIC_AUTH_TOKEN from env, so a runner-account residue would survive
into fork PR code on the pool — add it to the gate's scrub list (pinned
by both suites). The 'Configure persistent npm cache' step name promised
persistence the per-run HOME removes; rename it and record the accepted
cold-install cost (shared pnpm store stays a follow-up).
* fix(ci): scrub ANTHROPIC_AUTH_TOKEN at the no-AK gate; honest cache naming
Round-3 review: the Anthropic SDK's auth resolver back-fills
ANTHROPIC_AUTH_TOKEN from env, so a runner-account residue would survive
into fork PR code on the pool — add it to the gate's scrub list. The
value whitelist only iterates present keys, so the deletion mutation
escaped it; pin the key's presence-and-empty in the battery and the
28-key literal list. The 'Configure persistent npm cache' step name
promised persistence the per-run HOME removes; rename and record the
accepted cold-install cost (shared pnpm store stays a follow-up).
* test(ci): pin the no-AK grant, HOME template and reclamation path
Three assertions in the no-AK routing battery proved less than they read:
- the job's `permissions` was never inspected, so the read-only-checkout
grant that justifies routing untrusted fork code onto the pool could be
widened without a red test;
- `create.includes('HOME=')` is satisfied by `COREPACK_HOME=` and
`XDG_CACHE_HOME=`, so deleting the real HOME/USERPROFILE publication kept
the test green;
- the cleanup only had to contain the `qwen-no-ak-home-` prefix, so a path
that dropped `-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}` would still pass
while removing a directory nothing used.
Pin the grant by value, the publication by its printf template, and the
reclamation by the exact run-unique path the create step builds.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw48g5gafx
* test(ci): pin the realized HOME values in the shared ungated lane loop
R2-1: assert the create `home=` line, the gate QWEN_HOME and the cleanup
command by value, and require the exact set of `$GITHUB_ENV` writers. An
`echo` stand-in for the cleanup, a `-v2` create path and an appended second
HOME writer all kept the battery green under containment checks.
R1-2 / R1-1: hoist those pins into the shared `ungated` loop so
`lint_and_static` gets them too. Its create step was pinned by
`includes('HOME=')` (satisfied by `COREPACK_HOME=`) and its cleanup by
`includes('qwen-')`, so a dropped `HOME=%s` or a retargeted `rm -rf` survived.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw4h0sl6g9
* test(ci): red on later republishes of the isolated HOME keys
R3-1: the GITHUB_ENV equality guard read step 0 only, so a later step that
republished HOME reverted the run-unique home by last-write-wins while every
assertion stayed green — the isolation would silently stop applying to the
steps that run PR code. Scan the remaining steps for isolated-key
$GITHUB_ENV writers and for step-level HOME/USERPROFILE/COREPACK_HOME/
XDG_CACHE_HOME overrides, leaving the npm cache step's own key a legitimate
later write.
Mutation-verified on this head: appending printf 'HOME=%s\n' ... to
integration_no_ak's 'Configure per-run npm cache (self-hosted)' step and
adding a step-level `env: HOME` each turn the suite red (103 pass -> 102
pass / 1 fail); reverting restores 103/103.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw4ng9wvgi
---------
Co-authored-by: Qwen-Coder <[email protected]>
|
||
|
|
738c4bbf7e |
fix(serve): bound command snapshot replay memory (#16413)
* fix(serve): bound command snapshot replay memory Suppress unchanged command snapshots and retain only the latest full skill details so periodic refreshes cannot grow daemon heap indefinitely. Bound replay rings by serialized bytes while preserving ACP live delivery and reconnect IDs, and expose retention diagnostics. * fix(serve): avoid redundant replay seed serialization Size only retained complete command snapshots when seeding replay history, and forward the replay byte limit through the development daemon launcher. Pin snapshot accounting and lifetime eviction counters with independent regression assertions, and document the count/byte retention bounds and conditional command notifications. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
2666e3552d |
fix(core): make persisted session pagination safe for equal mtimes (#16024)
* fix(core): make persisted session pagination safe for equal mtimes
listSessions() ordered files by mtime alone and resumed the next page with a
strict `mtime < cursor`. An mtime is not unique — bulk copies, backup restores
and coarse-timestamp filesystems all produce ties — so a group of files sharing
an mtime that straddled a page boundary was dropped while hasMore still reported
progress, then the next page came back empty: 25 files at one mtime with size 5
showed 5 items in total.
Sort on (mtime desc, name asc) so the order is total, and let the cursor carry
that position: nextCursor is now `"<mtime>|<name>"`. A bare number is still
accepted with its existing strict `<` meaning, so callers holding a legacy
cursor keep working, and the daemon's opaque string cursor now passes the
composite form through instead of rejecting it as invalid.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-issue-patrol/jmvwpo7jxrk
* fix(cli): carry the composite session cursor type through callers
listSessions() now hands back a "<mtime>|<name>" cursor so a page boundary
inside a group of equal mtimes does not drop sessions. The widened
`number | string` contract left the CLI callers that store or compare that
cursor typed as `number`, which is a type error in `tsc --build` even though
nothing changes at runtime.
Widen the three consumers to `number | string`: the daemon's persisted scan
cursor, the live coordinator's paging plus its loop guard, and the session
picker state used by both the ink and opentui pickers. No behaviour change.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-issue-patrol/jmvwrtdc1rm
* fix(cli): route composite session cursors to the persisted listing
The persisted listing mints `"<mtime>|<name>"` cursors when a page ends
inside a group of files sharing an mtime, but the route still chose between
the persisted and the live listing path with a numeric-only predicate. A
composite cursor therefore fell through to the live path, whose parser only
accepts numbers, and the second page answered 400.
Export one predicate from session-list.ts carrying the same shape test the
cursor parser already applies, and use it for that routing decision.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-issue-patrol/jmvwtyj3bro
* fix(session-list): keep composite cursors usable by ACP and VS Code clients
listSessions mints "<mtime>|<name>" cursors whenever a page ends inside a
group of files sharing an mtime, but two consumers still parsed every cursor
as a number: ACP session/list rejected page 2 with "not a valid numeric
cursor", and the VS Code companion turned the composite cursor into NaN so
hasMore read false and only page 1 was ever listed. A malformed "|name"
cursor also slipped through the persisted-cursor classifier and answered 200
with an empty page instead of invalid_cursor.
- acpAgent: accept and forward "<mtime>|<name>" cursors to listSessions
- session-list: require a non-empty mtime prefix before classifying a
cursor as persisted
- vscode companion: pass the daemon cursor back verbatim, widen the cursor
type and treat only a non-empty cursor as "append to the current page"
Tests: ACP composite passthrough + empty-prefix rejection, VS Code
getSessionListPaged composite nextCursor round trip.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvxke6bm6i
* fix(vscode): keep composite session cursors type-safe in the companion
|
||
|
|
255d5cd2ae |
feat(web-shell): polish composer action menu presentation (#16181)
* feat(web-shell): polish composer action menu presentation * style(web-shell): use regular composer menu labels * fix(web-shell): address composer menu review feedback --------- Co-authored-by: 钉萁 <[email protected]> |
||
|
|
3663209c3d |
chore(ci): shorten stale PR window to 30 + 14 days (#16435)
* chore(ci): shorten stale PR window to 30 + 14 days The open PR backlog keeps growing, and the 60 + 30 day window only reaches PRs inactive for two months, leaving the 30-60 day band untouched. Lower the stale threshold to 30 days and the close window to 14 days, and raise operations-per-run to 300 so the first sweep can label the backlog in one or two runs. The stale/close messages and the fleet-shepherd design docs are updated to match. Co-authored-by: Qwen-Coder <[email protected]> * docs: keep the stale-window reference in the design current Avoid pinning the note to a merge-date guess: name the policy change instead, and reflow the paragraph to the file width. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
6fb741642f |
perf(managed-agent): take tool execution control off the tenant guards (#16415)
Every tool call admits an execution and authorizes its dispatch. Both went through lockBindingControl, which for a session-class binding (the default isolation class) took tenant placement, the publication guard, every slot and binding row of the Session and then its root, only to prove the Session is not CSI-pinned. lockExecutionControl now decides that under the Session root alone: the pin lives on the root, and profile slots and bindings require a committed pin. lockManagedSession and requireProvisionPin lock the root before slot and binding rows, so every placement owner shares the root-then-binding order that guard-free dispatch uses. Without an existing root, a locking read takes only a gap lock and orders nothing; dispatch then keeps the guarded path (a MySQL stress test reproduced the deadlock between the binding's unique-index and primary-key lookups otherwise). Admission still takes placement for the harness drain fence. |
||
|
|
da7af136e5 |
feat(skills): expose native archive routes and receipts (2/4) (#16422)
* feat(skills): add file-backed archive installation kernel * feat(skills): expose native archive routes and receipts |
||
|
|
17ad9cdb04 | feat(skills): add file-backed archive installation kernel (#16421) | ||
|
|
9b1ce46d77 |
feat(serve): add atomic user Skill activation (#16186)
* feat(serve): add atomic user Skill activation * test(serve): cover conditional user Skill activation capability * docs(serve): index user Skill activation endpoint * fix(serve): preserve User Skill refresh recovery |
||
|
|
4cea3a792f |
feat(cli): have an Ink TUI turn await its shared tool batch (#16385)
With experimental.unifiedAgentLoop on, the stream processor hands a shared round's tool batch to the round's submitQuery, which awaits it, its completion step and the round that step sends, once its own stream and activity ended. While another stream is active, which may be the stream that sent the round (a response of provider replays alone), the batch completes on its own as before. - The Ink executor takes a scheduler hold: the scheduler's completion callback returns once the completion step stopped the turn or is about to send the next round, as the current path holds it, up to the round whose calls need the scheduler. - A batch whose executed calls history all answered no longer sends its replay answers alone. The design doc describes PR 3b-2b. |
||
|
|
ecc64a735a |
feat(qqbot): deliver proactive loop messages as active group messages (#15635)
* feat(qqbot): deliver proactive loop messages to the group target
* fix(qqbot): fail blocked proactive deliveries and post loop output once
- pushProactive now raises ChannelProactiveDeliveryError when
sendMessageWithReplyContext reports a blocked outgoing route: the
blocked send resolves instead of throwing, so a loop delivery that
never reached the wire was being recorded as delivered. The block's
disposition is kept so the scheduler can distinguish a permanent
route failure from a retryable one; ACTIVE_MSG_DISABLED still throws
unwrapped from the send path.
- QQ's streaming chunk sink is skipped for unattended turns (loop and
webhook task, identified by the new protected isProactivePromptTurn
predicate), so the full answer is posted once through pushProactive
instead of being streamed to the group first and posted again. Base
chunk bookkeeping, lifecycle events and attended streaming are
unchanged.
- Tests: permanent and transient route blocks reject the loop and never
send; streamed chunks of an unattended turn produce exactly one send
carrying the full response.
* fix(qqbot): keep permanent delivery errors permanent and document proactive scope
- pushProactive rethrows QQ's permanent DeliveryError codes
(RETRY_EXHAUSTED, ACTIVE_MSG_DISABLED, FALLBACK_FAILED) as
ChannelProactiveDeliveryError('permanent', ...) so the daemon boundary
classifies a group that cannot receive active messages as rejected
instead of retryable. RATE_LIMITED stays transient.
- Drop the threaded-target guard copied verbatim from the base override;
every entry point already gates on supportsProactiveTarget and QQ never
produces a thread id, so the copy was unreachable drift.
- Name all four callers in the pushProactive doc comment (loop, webhook
task, background/named-task response delivery, daemon deliverProactive)
and state that background responses intentionally go out unanchored now.
- Document QQ in the proactive-delivery capability matrix and in the QQ
channel guide: active messages, group quota, ACTIVE_MSG_DISABLED and
the 5-failure auto-disable, no threaded targets, learned chat type
required.
* feat(qqbot): retire loops for chats that can no longer receive proactive messages
A loop created before a group disabled active messages, or before the bot
was removed from the group, kept firing: each run rebuilt a session, ran
a full unattended agent turn, and could never deliver. Both chat-wide
state changes now retire the chat's loops.
- base: add an optional chat-scoped controller lookup (listForChat) and
protected disableLoopJobsForChat(chatId), which disables every enabled
loop in a chat regardless of the creator's exact target; the CLI
controller implements the lookup over the loop store.
- qqbot: handleGroupDelRobot folds the disabled count into its existing
cleanup log line; handleGroupMsgToggle disables the group's loops when
active messages are turned off (both handlers become async and are
dispatched with an explicit rejection log).
- qqbot: refuse proactive targets for a group whose active-message
toggle is known to be off (supportsProactiveTarget), so loop creation
is rejected up front and a firing refuses before burning an agent
turn; direct messages never consult the group toggle.
- tests: creation refusal via the loop tool, run refusal with the prompt
never called, removal and toggle-off teardown against the real
controller, DM exemption, mid-flight toggle classification, and the
base helper's contract.
* test(qqbot): keep the loop-store integration test honest
- Pin a rendered next-fire ISO timestamp instead of base's 'n/a'
fallback: the copied controller now delegates nextFireTime the same way
the production controller does, and the store clock is fixed so the
value is deterministic.
- Redirect QWEN_HOME at the test's tmpdir before the channel constructor
runs and assert the channels directory lands inside it, so the test no
longer creates ~/.qwen/channels on the machine running it.
- Resolve the session through the locally constructed SessionRouter
instead of reaching the protected ch.router, which was a TS2445 that
no gate in the repo catches (test files are excluded from tsc).
* fix(qqbot): close the round-2 proactive delivery findings
- Resolve the target chat type through chatTypeFor() instead of
chatTypeMap, and refuse an explicit group target whose type is unknown,
so config-declared groups and never-learned groups are gated on the
same type resolveRoute routes on instead of burning an agent turn that
cannot deliver.
- Route background/named-task responses through pushProactive
unconditionally: a toggle-off group must not silently receive them
through the anchored passive reply path.
- Extract one PERMANENT_DELIVERY_CODES/isPermanentDeliveryError predicate
and use it at the proactive seam and the five existing classification
sites, preserving each site's behaviour.
- Fix the two comments that named a scheduler disposition check that does
not exist (the daemon classifyChannelDeliveryError is the only reader)
and word the blocked-route error as 'outgoing route unresolved
(<disposition>)'.
- Include the group openid in group-event failure logs and pin the
websocket dispatch rejection guard with a corrupt-store test.
- Stop retiring a group's loops when its active-message toggle turns
off: the toggle is reversible, the pre-flight gate already prevents the
turn burn, and the scheduler's 5-failure auto-disable retires the job.
Retirement stays on bot removal, where the gate cannot see the change.
- Pin the permanent/transient arms with FAILED-fallback and RATE_LIMITED
tests.
* test(cli): pin the chat-scoped loop lookup
listForChat decides which loops chat-wide teardown retires, and the QQ
suites substitute hand-rolled controllers whose filter mirrors it. Test
the real controller over a real ChannelLoopStore seeded with two chats
and two channel names, so dropping the filter (or only its channelName
clause) reds instead of silently retiring unrelated users' loops.
* docs(qqbot): record the proactive delivery contract
- Add the design record in English and Chinese covering the capability
flip and the four base call sites it activates, the single-delivery
rule for unattended turns, permanent/transient classification, chat
retirement on bot removal, and the group active-message toggle
contract.
- Complete the opt-in adapter list (DingTalk Workspace, Email with its
proactiveRecipients qualifier) and state QQ's target restrictions and
the unanchored background-response behaviour.
- Document isProactivePromptTurn in the base hook table and the
adapter-author proactive delivery section.
---------
Co-authored-by: Shaojin Wen <[email protected]>
|
||
|
|
e9a20d6ed7 |
fix(core): keep session list titles when a transcript is rebuilt (#16299)
* fix(core): keep session list titles when a transcript is rebuilt The sidebar label came only from the transcript: a custom_title record or the first user prompt. A transcript recreated without its head (missing file recreated on the next append, crash truncation, external cleanup) lost the prompt, so displayName became empty and the Web Shell rendered the session id prefix. Persist the list label to <sessionId>.title.json next to the transcript: - the first user prompt (source "prompt"); - custom titles from ChatRecordingService and SessionService.renameSession (source "manual"/"auto"). Reads keep the transcript authoritative and fall back to the sidecar. Archive, unarchive, delete, and storage relocation move or remove the sidecar with the session. Web Shell rows now render "Untitled session" / "未命名会话" instead of an id prefix. Design: docs/design/session-title-sidecar.md * fix(core): keep the list-label sidecar best-effort Write it after the transcript append and swallow its errors, so a partial Config or an unwritable home can never drop a user message. Update the deletion-cleanup ownership assertion for the extra title-sidecar check. * fix(core): align the title sidecar with the list reader - Seed the sidecar when a transcript is resumed (custom title first, then the recorded first-prompt display text) so sessions created before the sidecar existed gain one without another prompt. - Write the sidecar when forkSession commits a titled fork. - Store the prompt's displayText when present and share the reader's 200-code-point-plus-ellipsis truncation instead of a UTF-16 slice. - Pin the intended behaviour with tests for a text-less first prompt, resume seeding, and a titled fork; update both design documents. * fix(core): close the review findings on the title sidecar - Seed a resumed session with its actual first prompt: track that text outside the 20-entry title window and fall back to the record's text part when no display projection exists. - Never replace a persisted label from a later prompt. This also lets an image-only first prompt leave the slot open for the next text prompt. - Truncate and validate the sidecar name by code point so an astral prompt cannot store a lone surrogate or a discarded ellipsis. - Reconcile the title sidecar in both archive/unarchive conflict-resolution branches, where the hand-rolled sidecar set previously left it orphaned. - Update the workflow-mode e2e locator for the untitled row label, and cover the primary and fanout rows, archive moves, and storage relocation. - Correct both design documents: the list reads the sidecar when either the title or the prompt is missing, and a wholesale chats-directory clear also removes the sidecar. * fix(core): keep sidecar-backed session names consistent Review round follow-ups for the session title sidecar: - wrap the archive/unarchive conflict reconcile so a sidecar IO error no longer reports an already-completed transcript move as failed; - also fall back to a manual/auto sidecar in findSessionsByTitle, the picker prefix scan, and the unarchive collision guard, so a name the sidebar shows can be found with --resume and cannot collide unnoticed; - converge the live write and the resume seed on one label policy that mirrors the list reader (explicitly empty projection, Mod prompts); - stop a projector-restored session from being labelled with its first post-resume prompt, which is not the session's first prompt; - copy the source sidecar into a fork made without an explicit title; - return no name instead of another project's prompt text when the transcript is refused for project membership; - keep the id in the fan-out tooltip and the read-only row's accessible name, where it is not the visible text. Tests cover each fix, including the archived half of the lifecycle and a writer/reader pair on one filesystem; design docs updated in both languages. --------- Co-authored-by: verify-round <verify-round@local> |
||
|
|
c4f9899eea |
perf(managed-agent): take native journal snapshots off the tenant guards (#16410)
lockSnapshotWriter took tenant placement and the publication guard on each of its three calls per snapshot, so every snapshot of a tenant queued behind binding transitions, provisioning, retention, maintenance, collectors and writer acquisition. It now uses the guard-free publication writer. The build transaction cannot lock the Session root first, because its nested journal reads take read leases on that root and head. When it reaches the root it holds only the snapshot's own new rows. Any transaction that could hold the root and wait for those rows already closes a cycle with it today through the guards, so dropping them adds no cycle. Root and head order the writer checks, and the read lease keeps the copied resources. A pinned CSI Session still takes both guards behind its root. Co-authored-by: Claude Opus 5.5 <[email protected]> |