mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
main
12107
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a32684604a |
fix(core): surface Anthropic refusal details (#16079)
* fix(core): surface Anthropic refusal details Propagate Anthropic refusal stop_details through candidate finishMessage and the Finished event so both terminal UIs can show the provider's refusal category instead of a generic empty safety stop. Co-authored-by: Qwen-Coder <[email protected]> * style(core): apply Prettier to the refusal-detail diff The incremental lint gate selected 12 changed files and Prettier rejected four of them; this commit only re-wraps those lines, no behaviour change. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmvygjkzk7s * fix(core,cli): scope the refusal notice to safety stops Review follow-up on #16079: - Both renderers now prefer `finishMessage` only for `SAFETY` stops. The field is a provider wire field (`@google/genai` types it), so a Gemini turn carrying it could otherwise replace the curated notice for `MAX_TOKENS` and even add an info line on `STOP`. - The refusal category lookup uses an own-property check: a provider `category` of `__proto__` / `constructor` now falls back to the generic notice instead of returning an object that renders as `[object Object]`. - `llm-chat` deletes `finishMessage` together with the deferred `finishReason`, so the stream no longer yields a notice with no reason. - Tests: category table (all mapped categories + prototype key + unknown) and a non-safety renderer case pinning the curated notice. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw651acyim --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
588db8b137 |
feat(web-shell): show tool arguments and result in expanded cards (#15435)
* feat(web-shell): show tool arguments and result in expanded cards Cards outside the built-in detail renderers expanded to the same 80-character result summary shown while collapsed, so the chevron promised details it never delivered. Render the call's arguments and result instead, pretty-printing a JSON object or array and falling back to plain text, with the trajectory inspector's 4000/40 000 caps and a Show more action. Fixes #15280 * fix(web-shell): address tool card details review findings * test(web-shell): cover tool card review findings * fix(web-shell): preserve tool card JSON payload fidelity * fix(web-shell): sanitize tool payloads and cancelled results * fix(web-shell): bound tool payloads by render mode * fix(web-shell): memoize tool card payload formatting * fix(web-shell): size tool card JSON by render cap and scope its rules * fix(web-templates): raise document export budget for tool cards * fix(web-shell): address round 3 tool card review suggestions * fix(web-shell): gate Show more on escaped payload length * fix(web-shell): stop rendering the cancelled input dump as a result A cancelled call that recorded no output carries the daemon's redacted input dump in rawOutput.text, written there as the same string as reason. cancelledOutputText accepted it as output, so the expanded card showed the call's own arguments twice, once as Result. Skip a text candidate that equals reason while keeping output/stdout. * test(web-shell): pin the arguments escape guard and the inline cap Both behaviours were unpinned: replacing the arguments-side sanitized-length comparison with the raw length, or lowering MAX_TOOL_PAYLOAD_LENGTH to 400, left the suites green. JSON.stringify escapes C0 bytes but not the bidi controls, so an object argument carrying U+202E is the reachable shape where escaping moves the payload past the cap. --------- Co-authored-by: Shaojin Wen <[email protected]> |
||
|
|
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]> |
||
|
|
f8256e8222 |
fix(release): derive the next patch when the preview baseline lags latest (#16470)
* fix(release): derive the next patch when the preview baseline lags latest (#16466) Co-authored-by: Qwen-Coder <[email protected]> * fix(release): derive the stable bump from the rollback-aware latest (#16466) Co-authored-by: Qwen-Coder <[email protected]> * refactor(release): share the published-latest bump helper (#16466) Co-authored-by: Qwen-Coder <[email protected]> * fix(release): refuse cross-line stable versions from older refs (#16466) Co-authored-by: Qwen-Coder <[email protected]> * fix(release): refuse stable releases from refs behind latest (#16466) Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> * fix(release): refuse cross-line and stale stable derivations (#16466) Co-authored-by: Qwen-Coder <[email protected]> * test(release): pin the cross-line guard's strict boundary (#16466) Co-authored-by: Qwen-Coder <[email protected]> * fix(release): refuse a derived base above the checked-out ref (#16466) Co-authored-by: Qwen-Coder <[email protected]> * fix(release): keep same-line next-patch promotions allowed (#16466) Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: qwen-code-dev-bot <[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 |
||
|
|
fd3324b7cd |
fix(serve): serialize overlapping cleanup retries (#15482)
* test(serve): reproduce overlapping cleanup retries * test(serve): wait for the first cleanup pass before overlap * fix(serve): serialize overlapping cleanup retries * test(serve): cover refused overlapping cleanup retries * test(serve): capture the retry scheduler through mock contexts * test(serve): cover cleanup retry guards under contention * test(serve): keep regression setup within the shared wait budget * test(managed-agent): isolate H2 catalogs by test class --------- Co-authored-by: Shaojin Wen <[email protected]> Co-authored-by: Shaojin Wen <[email protected]> |
||
|
|
0b54b9be99 |
fix(managed-agent): restore two runtime-broker classes to their size ratchet (#16542)
ClassSizeRatchetTest fails on main itself: RuntimeBrokerService is 2947 lines against a 2943 ceiling and RuntimeBindingProvisioning is 1327 against 1326. Both overruns come from #16535 replacing inline fully qualified names with imports, which adds import lines while the usage lines keep their count — 4 imports in RuntimeBrokerService and 1 in RuntimeBindingProvisioning — without updating the ceilings that #16501 had just recorded after the extraction. The SDK Java lane runs the merge ref, so every PR that merges main inherits the failure. The ratchet only lets ceilings go down, so reclaim the lines instead of raising the pins: join expressions that were already split across two lines. RuntimeBindingProvisioning's single Objects.equals operand, and the four BrokerValues.requireId assignments in release() and releaseMount(). Every joined line is at most 88 characters; the diff is formatting only and keeps #16535's import style. ClassSizeRatchetTest red before / green after; RuntimeBrokerServiceTest 199 green; runtime-broker checkstyle and spotbugs clean. No pin was edited. |
||
|
|
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]> |
||
|
|
47d41dbf71 |
fix(channels): keep the cursor write alive when the temp file mode cannot be tightened (#16257)
* fix(channels): keep the cursor write alive when the temp file mode cannot be tightened Mounts without Unix permissions (SMB/CIFS, exFAT, some FUSE) accept the write but refuse chmod, so the unguarded temp-file chmod made every save throw before the rename and the cursor was never persisted. Guard it like the directory chmod and pin the behaviour with an EPERM test. Also seed the scope directory at 0o755 in the permissions test so it proves the tightening, and pin that an empty scoped cursor starts fresh instead of falling back to the legacy global file. * test(channels): assert the injected chmod fault fired in the cursor guard test 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. |
||
|
|
9d1cf396a5 |
fix(web-shell): fall back to English copy when the UI language has no relay dictionary (#15474)
* fix(web-shell): fall back to English copy when the UI language has no relay dictionary * test(web-shell): point the relay fallback case at an unshipped locale and pin the translator fallback The relay regression case used 'ru', which #13685 shipped with its own relay entry, so the '?? COPY.en' arm never executed and the guard was unpinned (mutation matrix: M1/M2/M3 survived). The case now uses 'fr', a locale outside the shipped languages union, which kills the fallback mutants. The whole-shell translator gets the same treatment: 'getTranslator' falls back to the EN table for an unshipped locale, and a new i18n case pins it. --------- Co-authored-by: Сергей Панченко <[email protected]> Co-authored-by: Shaojin Wen <[email protected]> |
||
|
|
c1aa266e82 |
perf(core): compare a grown local journal prefix in chunks (#16507)
- Re-read a file that grew during the read through one 64 KiB chunk instead of a second buffer of the whole size, so an unbounded read of a transcript being appended to holds one copy of it. - Name the same-length rewrite within one timestamp tick as a cost decision in the comment. - Inject the read races through one helper, and cover a same-length rewrite with moved timestamps, a bound below the file size, a shrink that stays above the bound, a short comparison read and a comparison over several chunks. Drop two cases that duplicated others. |
||
|
|
4d61487419 |
ci(release): only file issues on failure and reconcile stale ones (#15852)
* ci(release): file failure issues only for failures and close stale ones The VSCode companion and Chrome extension notifiers filed an issue when a job was merely cancelled, and nothing closed that issue once a later attempt shipped, leaving a live failure report against a released version. Drop the cancelled arm from both notifiers and add a success-gated reconcile-failure job to each, reusing the shared release step. That step now takes the title prefix and release version so all three release workflows can close the bot-authored issue an earlier attempt filed. Fixes #15840 * test(release): pin the version boundary when closing a stale issue A nightly sibling of the same release sorts first in the gh issue list result, so dropping the " on " suffix from the title guard silently closes another release's issue. The fixture now covers that ordering and the boundary mutant is killed. * test(release): tie each reconcile prefix to the title its notifier files The fan-out suites asserted `FAILURE_TITLE_PREFIX` against a hand-written constant, so renaming the notifier's `--title` left both green while the real reconcile would match no issue and the stale release-failure issue would stay open against a shipped release (QwenLM/qwen-code#15852). Read the prefix out of the notifier's own `--title` argument and assert it starts with the prefix the reconcile job passes, instead of restating the literal a third time. * test(ci): pin the version token and separator in the release title contract reconcile-failure reads a notifier title through startswith(prefix + version + " on "), but the suite only pinned the prefix. A notifier that dropped the version or separated it with something other than " on " closed nothing while both suites stayed green, which is a silent failure of the stale-issue cleanup (#15840). Derive the rest of the title from the notifier's own --title, accept the version variable with or without its :-unknown default, and assert the reconcile job reads the same variable the notifier writes. --------- Co-authored-by: Shaojin Wen <[email protected]> Co-authored-by: 2SinceJuly <[email protected]> |
||
|
|
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]> |
||
|
|
c2e77d1473 |
refactor(contracts): import inline fully qualified names in contracts (#16534)
Replace 183 inline fully qualified type names across 56 Contracts files with ordinary imports, using the same skip rules as the Runtime batch. The compiled classes are unchanged: all 604 classes are identical under javap -c -p ignoring constant-pool indices. Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
1762efe4cb |
fix(channels): keep a delivered turn completed when the notice send fails (#16512)
* fix(channels): keep a delivered turn completed when the notice send fails The stacked-skill turn notice is delivered after the answer, inside the same try block that guards the delivery. A failing notice send (for example a QQ 429 RATE_LIMITED DeliveryError) bubbled into the catch that emits failed(phase: 'delivery') and rethrows, rewriting an already delivered turn into a failure even though the user received the answer. Guard the notice send and log the failure to stderr instead, matching the deliveryStarted invariant: once delivery started, the turn's outcome is fixed. Refs #16441 * fix(channels): fail the turn when the notice was its only delivery The prompt result is `turnNotice || body`, so with an empty body the notice send is the turn's only delivery. Swallowing its failure reported the turn completed while nothing reached the user; rethrow so the outer catch emits failed:delivery, as main did. Pin the notice-only branch and the log text. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw5ylt0cid * fix(channels): log which notice was dropped when its send fails R2-1: the catch that keeps the turn `completed` after a failed notice send logged only the transport error. The notice text had already been sliced out of the answer, so the log line was the last place that knew what the user never received. Include it bounded and sanitized, matching the file's logging convention, and pin it in the existing regression test. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw651acyim --------- Co-authored-by: zzj <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
1b9aef2264 | refactor(adapters): import inline fully qualified names in adapters (#16533) | ||
|
|
157664acb2 | refactor(runtime-broker): import inline fully qualified names (#16535) | ||
|
|
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
|
||
|
|
222ca313e2 | test(ci): harden TUI workspace reconciliation (#16033) | ||
|
|
0acd2851cd |
refactor(runtime-broker): extract binding provisioning from RuntimeBrokerService (#16501)
* refactor(runtime-broker): extract binding provisioning from RuntimeBrokerService RuntimeBrokerService still carried the whole Runtime binding lifecycle: durable provisioning and attestation, the bounded reconciliation loop, lost-binding cleanup and the trusted maintenance recovery path. Those members only depend on each other and on the operation claim, so they move into a package-private RuntimeBindingProvisioning collaborator, alongside the existing dispatcher, reconciler and recovery classes. This is a pure move. The service delegates binding acquisition, recovery, the deadline and step-timeout helpers and attestation checks to the new class. The provision failure handler is now owned by the collaborator, which removes the service's constructor-time method reference cycle. BindingRenewal stays in the service because the drain path also uses it; the collaborator receives its constructor as a factory. RuntimeBrokerService shrinks from 4174 to 2948 lines. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(runtime-broker): tighten class-size ratchet after binding extraction --------- Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
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
|
||
|
|
5890329390 |
fix(ci): probe the host egress proxy so mainland release hosts can build the sandbox image (#16281)
The docker-container driver's buildkitd inherits neither the runner's proxy env nor the host network namespace. On ecs-qwen-hz1-17/18 the DNS answer for registry-1.docker.io is poisoned, so the pinned node:22-slim resolution dies with a direct-dial timeout before any application step — five consecutive image-build failures (#16039, #16162) that forced ECS_RELEASE_HOST_RUNNER_ENABLED=false. Probe the fleet-managed proxy marker and inject network=host plus env.*_PROXY driver opts only when the proxy answers a live registry probe. Hosted and HK runners have no marker file, so the probe emits nothing and their builds keep the default direct route. Verified live on hz1: unopted builder reproduces the incident, opted builder resolves the pinned digest for both platforms. Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
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]> |
||
|
|
29fa7bbe06 |
chore(types): compile cleanly under the TypeScript 7 native compiler (#16463)
* fix(types): compile cleanly under the TypeScript 7 native compiler tsgo (@typescript/native-preview) reported 48 errors across the typecheck projects while tsc 5.8 reported none. This removes all of them without changing what tsc emits, so a tsgo typecheck lane or a later compiler switch can start from a clean tree. - Drop `baseUrl` from five tsconfigs. TypeScript 7 removed it, and every `paths` target here is already relative to its tsconfig. - Set `rootDir` where TypeScript 7 no longer infers it (web-shell declarations, sdk-typescript), and list node types explicitly for cua-driver now that @types are no longer included by default. - Annotate Buffers built by Node's Buffer factories as `Buffer<ArrayBuffer>` (or widen to `Buffer` where a variable later receives stream chunks); the ES2024 lib no longer treats ArrayBufferLike as an ArrayBuffer, so these failed at fetch, Blob, and Response call sites. The two call sites that take caller- or interface-supplied bytes assert `BodyInit`, matching the existing XMLHttpRequest upload path. - Remove three redundant `?? []` / `?? undefined` operands that the newer checker reports as unreachable. - Declare `*.css` modules for the two integrations that import stylesheets, now that side-effect imports are checked. * fix(types): include web-shell client declarations in the build config The config's include listed only client/index.tsx, so client/vite-env.d.ts and client/local-files/file-system-access.d.ts never entered the program. Removing baseUrl unmasked 227 tsgo errors (and 222 under tsc 5.8) for the CSS module and File System Access declarations they provide. --------- Co-authored-by: Shaojin Wen <[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]> |
||
|
|
f087e699af |
fix(core): register promoted initial commits in unborn repos (#16081)
* fix(core): register promoted initial commits in unborn repos A promoted command captured its pre-spawn HEAD as null both when the repository was unborn and when the probe failed. The registration path then skipped both cases, so amending the agent's own initial commit was blocked.\n\nCapture the pre-spawn state as either a SHA or an explicitly unborn HEAD. The promoted lineage check now requires a null predecessor only for the unborn case, while repository or probe failures still skip registration. Add a real-git regression for a promoted initial commit. Co-authored-by: Qwen-Coder <[email protected]> * fix(core): fail closed when the pre-spawn HEAD probe is terminated Only a genuine git non-zero answer (status 128, no signal) proceeds to the repository probe. A killed, timed-out, or failed-to-spawn HEAD lookup now returns null instead of asserting a verified unborn repository, which the promoted path could otherwise pair with a single-entry reflog to register a pre-existing root commit the agent never made. Co-authored-by: Qwen-Coder <[email protected]> * style(core): prettier formatting for probe regression test Co-authored-by: Qwen-Coder <[email protected]> * docs(core): scope the lineage guard's fail-closed claim to born HEAD R1-1: the docblock stated "a foreign commit never earns an exemption" as a fail-closed property, which the unborn arm makes false — `null` is a sentinel every root commit satisfies, so a root commit somebody else lands during the promoted window is indistinguishable from this command's own. Say that, and scope the five-star guarantee to a born HEAD. The behaviour is the trade tracked on #12523; only the written invariant was wrong. R1-2: pin that the PATH `git` shim is live before relying on it. Both closing assertions also hold when the shim is bypassed (the lineage check then rejects on a non-null predecessor), so the row could green without exercising the probe-termination path it exists to pin. Verified by mutation: changing the shim's match from `--verify` to `--bypass` turns this row red. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw1dkg4zbx * docs(core): scope the promoted-lineage claim to what the predecessor proves The `requirePredecessor` paragraph said "a foreign commit never earns an exemption", and the promoted call site said the guard keeps the registered SHA one this command created. The lineage check only proves the newest reflog `commit:` entry moved HEAD from `preHead`; a foreign commit landed directly on `preHead` while this command's own commit never landed has that same predecessor and still registers, so both statements overclaimed. Scope them: born-HEAD narrowing is not closing, and the fail-closed guarantee holds only once this command's own commit landed first. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw2g5c3hdf --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
2b94314b49 |
fix(cli): scrub advisor selector from startup errors (#16083)
Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
bbff05f035 |
fix(serve): treat a payload-invalid start reply as a definite refusal (#16460)
`HostedWorkspaceBroker.execute()` only treated the two 409 conflict codes as a definite "never started" refusal. When the Runtime Broker rejected a deferred tool payload it could not encode (an unpaired surrogate) it answered `:start` with HTTP 400 `runtime_payload_invalid`, which the client read as a lost reply: it polled the observation endpoint until `observationMs` (120s, up to 180s) and then surfaced the session as Hosted recovery-blocked with the workspace lease still held. A payload the Broker refused to parse never became an invocation, so no poll can turn it into a reply. Rethrow the 400 `runtime_payload_invalid` rejection from the `:start` catch alongside the 409 conflicts; the polling path and the unknown-execution reconciliation are unchanged. The new test drives a fixture Broker that answers `:start` with that 400 and asserts the rejection propagates with only the `:start` request observed; it fails before this change because the extra observation GET appears in the request path list. Patrol-Run: qwen-issue-patrol/jmw4xgcu3zh Co-authored-by: yiliang114 <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
3d9988abc2 |
fix(cli): surface stacked-skill overflow in non-interactive and ACP runs (#16404)
* fix(cli): surface stacked-skill overflow in non-interactive and ACP runs `parseStackedSlashCommands` already reports `exceededMax`, and the two interactive surfaces warn about it. The non-interactive path (`handleSlashCommand`) ignored the flag, so headless (`-p`, JSON, stream-JSON) and daemon/ACP runs silently dropped the extra `/skill` tokens with no signal at all. - carry the flag out of the stacked branch as `stackedSkillOverflow` on the `submit_prompt` result, using the same copy as the interactive surfaces; - headless writes it to stderr, so it never becomes a `message` of type `error` and the run keeps its exit code; - ACP emits it as a client-visible slash-command notice before the prompt runs. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-issue-patrol/jmw3ofzhmya * fix(channels): carry the stacked-skill overflow notice with the turn reply Channel bridges deliver only prompt()'s resolved text, so the overflow notice emitted on the slash-command channel was shadowed as soon as the turn produced model text and channel users never saw the warning. Tag the notice in `_meta` and route it into the turn's text in both bridges. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw3tg0yrfb * fix(channels): keep the turn notice when a boundary clears the chunks The notice was pushed into `chunks`, which `clearChunks` empties on every `responseBoundary`, and the `per_task` branch never read it at all, so the stacked-skill warning still disappeared on any turn that made a tool call. Give the notice its own buffer in both bridges that boundaries never clear, and compose it into every return path (turn text and `per_task` result). The slash-command slot keeps its existing precedence and the pinned response-boundary tests stay unchanged. Tests: tool-call-boundary and `per_task` arms for both bridges, plus a Session-side test that the producer tags the overflow notice with `turnNotice: true`; each new arm reds when the buffer is removed. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw3zvicvfk * test(channels): pin the turn notice on the real boundary and empty replies Address the follow-up review on the turn-notice carry: - R3-1: the daemon boundary test fired `responseBoundary` before the queued notice was consumed, so it cleared nothing; drive the boundary from an in-turn `tool_call` instead and assert it goes red when `clearChunks` regains `turnNotice = ''`. - R3-3: the `turnNotice` wire key was a bare literal on every side; export `CHANNEL_TURN_NOTICE_META_KEY` beside the sibling keys and use it in the producer and both bridges. - R3-4: a notice-only turn returned `"<notice>\n\n"`; drop the separator when the body is empty and cover the exact resolved string. - R3-5: cover the per-session filter in both bridges so another session's notice cannot prefix this turn's reply. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw445tvbfr * fix(channels): deliver skill overflow notices outside response buffers --------- Co-authored-by: yiliang114 <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: zzj <[email protected]> |
||
|
|
e3cbea0096 |
fix(cli): surface workflow agent approvals in the interactive TUI (#16425) (#16432)
* fix(cli): surface workflow agent approvals in the interactive TUI (#16425)
A backgrounded workflow agent that hit a tool-approval gate deadlocked in
the interactive TUI: the registry parked the approval, the footer showed a
"needs approval" badge, but there was no way to answer it, so the run never
made progress. Two independent defects caused it.
1. `WorkflowRunRegistry.parkApproval` treated the display-only
`approvalChangeCallback` as a sufficient host channel. A host that could
only repaint the badge therefore accepted the approval and then had
nothing to answer it with. It now fails closed unless a real
`approvalRequestCallback` responder is registered, so a badge-only host
rejects the park and the caller sees a cancelled request instead of a
silent forever-wait.
2. `useBackgroundTaskView` never registered a responder at all, so even a
host that wanted to answer had no channel. It now registers its snapshot
refresh as the approval-request responder (and clears it on unmount),
which makes the parked approval part of `entries` — the surface the
background-tasks dialog renders and settles through
`resolvePendingApproval`.
`BackgroundTaskViewProvider` also promotes a parked workflow approval to
the detail view once per `approvalId`, because the approval can only be
answered from there; keying on the id keeps a user's "close" from bouncing
straight back into the dialog on the next refresh.
Scope: interactive TUI workflow approval path only. Headless and ACP hosts
are untouched.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-issue-patrol/jmw4ig8e5z4
* fix(core): drop the redundant responder guard after the park gate
TS narrows approvalRequestCallback to non-undefined once the park gate
requires it, so the extra truthiness check is dead and fails tsc (TS2774).
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-issue-patrol/jmw4ig8e5z4
* fix(cli): type the workflow approval fixture and format the new tests
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-issue-patrol/jmw4ig8e5z4
* test(core): register a real approval responder in the orchestrator bridge test
The narrowed park gate rejects an approval when the registry has no
approvalRequestCallback, so a badge-only host (change callback only) leaves
pendingApprovals empty and the bridge test times out. Register the responder
the production host also provides, matching the gate's contract.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw4lb42xgf
* fix(cli): surface each parked approval once, in list mode (#16432 R1-1, R1-2)
The single-id latch keyed on the first pending entry's head approval, so a
second approval parked behind a still-parked head never surfaced. Latch a
set of approvalIds and select the entry holding an unsurfaced one.
Auto-open also landed in detail mode, which arms the tool confirmation
with focus while unmounting the composer: a stray Enter aimed at the
prompt approved the background command. Auto-open now lands in list mode
with the row selected; entering detail stays an explicit navigation.
Tests: flip the surfacing contract to list mode, add the masked-approval
regression (red on the single-id latch), drop a dead assignment (R1-8).
* fix(cli): disarm auto-opened task dialog and latch only the displayed approval head
An auto-opened background-task dialog unmounts the composer and keypress
dispatch is a broadcast, so a letter still aimed at the composer could
cancel (x) or pause (p) the selected workflow without confirmation, and a
stray Enter could arm the approval. Tag auto-opens and disarm x/p/Enter in
the dialog's own list branch until the first explicit navigation key
acknowledges the open; user-initiated opens keep immediate behavior.
The surfacing latch recorded the first unsurfaced approval, but the detail
view renders only pendingApprovals[0]; an approval parked behind a still-
parked head was marked surfaced without ever displaying and never prompted
once it became head. Latch only the displayable queue head so the next
approval prompts after the head drains.
Addresses the two Criticals from the #16432 human review.
* test(cli): pin the closed-dialog guard on the approval auto-surface effect
The surfacing effect's dialogMode guard had no test that failed without
it: every existing case fired refreshes only while the dialog was
closed. Add a case that opens the detail view, parks a workflow approval
mid-read, and asserts the view and selection stay put. Deleting the
guard turns exactly this case red (#16432 R2-3).
* fix(cli): hold the auto-open disarm until a real navigation key
- Acknowledge the auto-open only on arrow keys. The selection matchers also
carry bare k/j and Ctrl+P/Ctrl+N, and ordinary typed letters satisfy
them, so prose typed at the composer the dialog just unmounted released
the disarm and the next x/p/Enter reached the run (#16432 R2-1).
- Swallow closeDialog while the auto-open is unacknowledged: Escape, Left
and Ctrl+C burned the add-only latch before the prompt was ever seen and
the workflow fell back to the badge-only hang this PR removes. Ctrl+C
reaches the same path through closeAnyOpenDialog (#16432 R3-1).
- Unfocus the pill in the auto-open writer, matching openDialog, so the
pill's Enter cannot run openDialog() and clear the disarm (#16432 R3-2).
- Advertise only "arrows activate" while disarmed instead of Enter view /
x stop, which were swallowed (#16432 R3-3).
- Record the parked head ids at close so an acknowledged close stays
closed while another workflow is already parked, and a genuinely new
approval still surfaces (#16432 R3-5).
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw5b11gghf
* style(cli): wrap the over-width hint condition to the prettier limit
The R3 hint-row re-indent left the paused-agent condition one column
over the 80-char limit, failing the Lint & Static gate at
|
||
|
|
49ac6dae86 |
fix(sdk): name daemon UI tool blocks from _meta.toolLifecycle frames (#16306)
* fix(sdk): resolve tool name from _meta.toolLifecycle for web-shell labels Foreground subagent tool calls render as "unknown Tool" in the web-shell session view because the daemon normalizer's tool-name chain never reads `_meta.toolLifecycle.toolName`, the only name carrier on lifecycle frames emitted by transcript-replay. Such a frame projects a block with no toolName and the generic "Tool" title, and the web-shell adapter's `resolveToolCallName(...) || 'unknown'` renders that pair verbatim. Append the lifecycle name to the tail of the resolution chain so explicit `title` / `toolName` / `_meta.toolName` keep their precedence, and leave the silent-shell lifecycle discard guard untouched. Fixes #16301 Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-issue-patrol/jmw20fhpuwp * fix(sdk): name subagent tool blocks from lifecycle metadata The metadata-only guard dropped the one frame that carries a subagent tool call's name, so the previous fallback never ran and #16301's "unknown Tool" label survived. Forward a name-only tool.update from those frames and let the reducer name an existing block by toolCallId: it never creates a card and never replaces a human-readable title. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw23adg4cx * fix(sdk): close R2 review on lifecycle tool naming - Skip name-only tool.update events in the terminal renderer: they carry no status, so rendering one printed "tool undefined <name>" per tool call. - Return before the copy-on-write path when a nameOnly frame targets an already-named block, and keep retainedBytes accounting exact on rename. - Add the status-bearing lifecycle arm test and the nameOnly renderer test; both go red when their guard is removed. - Rewrite the comment above the lifecycle branch, which still claimed those frames are dropped. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw2opz8jds * fix(sdk): name mid-run attach tool blocks from the lifecycle frame R3-1: the name-only arm returned early when no block existed for the call id, so a client that attached mid-run (lifecycle frames before any create frame) still showed "unknown Tool". Park the late name per call id and seed the create path with it. R3-2: read the lifecycle fallback through extractToolLifecycle so a status-bearing frame makes the same v1 validation decision as the metadata-only arm. R3-3/R1-2: pin the create path with producer-shaped frames (no fabricated status), the no-overwrite guard, and the v2 rejection case. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw2zfs5be7 * test(sdk): pin the parked-name bound and name what the attach case pins Review R4-2: nothing pinned MAX_PENDING_TOOL_NAMES or its oldest-first eviction, so deleting the guard left the suite green. Add a case that parks 257 distinct call ids and asserts the map stops at 256, `call-0` is gone and `call-256` survives. Verified load-bearing: disabling the eviction guard fails it with 257 keys. Review R4-4: the "mid-run attach" case passes unchanged on the base tree, because its create fixture carries `title` and `_meta.toolName` — the pre-existing chain already reads those, so the parked name is consumed and discarded. Rename it to what it actually pins; the sibling case `seeds the late name when the status frame carries none` is the one that discriminates the parking path. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmw3p5pfkf6 --------- Co-authored-by: yiliang114 <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
791989cdb8 |
fix(vscode): keep sibling model settings when writing provider config (#15829)
* fix(vscode): keep sibling model settings when writing provider config
Configuring a Coding Plan or a model provider from the panel replaced the
whole settings.model object with { name }, silently dropping keys the CLI
owns: baseUrl, reasoningEffort, maxSessionTurns and the run budgets.
Write name and its paired baseUrl key by key instead, matching the other
sections in the same writer and the CLI's own writers. The baseUrl is
written rather than merely preserved because the resolver's merge invariant
needs a present value (or an empty-string tombstone) to override a stale
one in a lower-priority scope.
Fixes #15822
* test(vscode-companion): pin region and custom-endpoint baseUrl writes
The api-key fixtures reused the fallback literal, so a regression that
dropped the modelProviders lookup kept the suite green; likewise only the
china branch of the Coding Plan model.baseUrl write was pinned. Seed a
custom gateway URL plus an active-model-absent fallback case, and assert
the global region writes its own endpoint.
* test(vscode-companion): pin the api-key install path's model baseUrl
* test(vscode): pin the empty baseUrl tombstone for id-only installs
Subscription install plans select a model by id alone, so the only thing
clearing a stale model.baseUrl is the empty-string tombstone. The existing
install-path cases always pass a baseUrl, leaving the serialization half of
that branch unasserted on disk.
* fix(vscode): take the OpenAI default base URL from the core helper
The active-model write re-spelled https://api.openai.com/v1 while the paired
provider entry and the login flow both go through
getDefaultBaseUrlForProtocol. Moving that default in core would then leave
this path behind, so call the helper instead.
---------
Co-authored-by: 肖洪进 <[email protected]>
Co-authored-by: 2SinceJuly <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
|
||
|
|
930962ab51 |
fix(managed-agent): let a late snapshot publication wait for the claim instead of losing deadlocks (#16489)
* fix(managed-agent): let a late snapshot publication wait for the claim instead of losing deadlocks Since #16143 resource_key is a secondary unique index, so a second publication's manifest upsert queues for the first's uncommitted row with a next-key lock. Every page the first Build inserts into that lock's gap rolls the queued publication back, and on longer journals #16446's three attempts run out: on a 31-page journal a late second publication failed for 15 of 40 snapshot ids. The claim now locks the manifest row by key first. At READ COMMITTED that takes a record lock when the row exists, which does not block the inserts into the gap, and only a missing row is upserted. Two publications that both find no row can still meet in the upsert; #16446's retry handles the one rolled back, whose locking read then waits for the winner. The MySQL cases pin both paths and assert the key order they depend on. * test(managed-agent): pin the snapshot claim's reads, upserts and rollback interleaving - State in the claim's Javadoc that the wait on the manifest row lasts at most innodb_lock_wait_timeout and that a lock wait timeout (1205) is not retried, only deadlock victims (1213) are. - Count the claim's locking reads and upserts in the test JdbcTemplate and assert them in the new concurrent cases, so a reworded claim statement fails those cases instead of turning them into plain races. - Cover a publication queued on a claim that rolls back: it finds no row and publishes the snapshot itself. - Check that publications run at READ COMMITTED, which the claim's comment relies on. - Read the monitor fixture through one helper. |
||
|
|
2258518d46 |
refactor(runtime): import inline fully qualified names in runtime modules (#16504)
The runtime provisioner, transport and host modules spelled 80 type references as inline fully qualified names. They now use imports. The conversion only touches code (never strings or comments) and leaves a name qualified whenever importing it could change what any name in the file resolves to: an existing or same-package type with that simple name, a declared or nested type, java.lang, a bare identifier already in use, or a wildcard import. The compiled bytecode is unchanged: javap -c -p of all 49 classes in the seven modules is identical before and after, ignoring constant-pool indices. Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
a84493d628 |
fix(core): report a cancelled shell as interrupted, not completed (#15836)
* fix(core): report a cancelled shell as interrupted, not completed The shell tool kills the process group on a user cancel but returns an ordinary result, so the scheduler's post-execute cancellation branch labelled the call "already completed" and told the model not to retry work that never finished. Let producers mark that outcome on the result; the shell tool sets it on a non-timeout, non-promote-refused abort. Closes #15833 * fix(core): keep a cancelled shell's partial output with the notice The scheduler replaced the whole model-visible payload with the cancellation notice, so the output a killed command had already produced disappeared even though the shell tool reports it. Append the producer's own interrupted text when it sets interruptedBeforeCompletion, and pin the marker's negative cases that the existing suites did not distinguish. * fix(core): flag returned cancellations as interrupted The monitor, create_sub_session, ask_user_question and workflow tools report a cancelled call by returning a result instead of throwing, so the scheduler's returned-means-finished assumption told the model the work had completed. They now set interruptedBeforeCompletion, which the scheduler already honours for the shell tool. * fix(core): mark only aborted unanswered questions as interrupted A plain decline is a completed decision. Setting the marker on it is inert today, but would report a finished interaction as "do not retry" if a future read site consumed it. * fix(cli): report an interrupted shell as interrupted in ACP sessions --------- Co-authored-by: 2SinceJuly <[email protected]> Co-authored-by: Shaojin Wen <[email protected]> |
||
|
|
da30adcc2e |
refactor(managed-agent): fold the last CSI statement helper copies (#16503)
The CSI ready admission and application progress stores kept their own copies of the 10 second query-timeout statement helper because #16485 was still editing the adjacent lines when #16486 introduced the shared helper. Both now use the shared one; the timeout is unchanged. Co-authored-by: Claude Opus 5.5 <[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]> |
||
|
|
0a562773f7 |
fix(core): match one sed character for . and bracket expressions (#15701)
* fix(core): match one sed character for . and bracket expressions The simulated sed path compiled patterns without the s and u flags, so `.` did not match `\r` in CRLF files and astral characters were matched as two UTF-16 code units, leaving a lone surrogate that was written as U+FFFD. Compile with su/gsu and escape a literal ] outside bracket expressions so the stricter u mode still accepts patterns such as [\[\]]. Closes #15675 * ci: rerun the checks on the unchanged tree * fix(core): decline sed escapes that mean something else under u `\p{…}`, `\P{…}` and `\u{…}` are literals to sed but Unicode property and code-point escapes to a `u`-flagged regex, so the simulator accepted patterns real sed rejects or leaves alone and wrote the simulated bytes to disk. Widen the divergent-escape guard and pin the declines, plus the `u`-only rejection class the flag introduced. * fix(core): judge a simulated sed edit by the bytes it will write On a CRLF file, `s/.$//` consumes the trailing carriage return, but the write path restores CRLF, so the edit reported success while the bytes on disk were unchanged. Compare the prepared write content against the original instead of the parser's intermediate string. * fix(core): judge a simulated sed edit by the content a read-back returns The write-back prediction decoded the prepared bytes as UTF-8 and compared them with the read content, so a file carrying a BOM or a non-UTF-8 encoding never matched and the false `sed edit applied` this PR removes survived. Compare content strings instead, and only predict the CRLF normalization when the writer applies it: a client-side write stores the content verbatim, so `s/\r$//` is a real edit there and must not be suppressed. * fix(core): decline \0 in simulated sed patterns `\0` is NUL to a `u`-flagged regex but a literal `0` to sed, so the simulator found no match and answered `sed edit made no changes` instead of falling back to the real binary. * fix(core): predict the CRLF restoration for every delegated write * test(core): pin the \0 decline in simulated sed patterns * fix(core): decline mixed POSIX bracket classes in simulated sed * 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. * 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 * feat(skills): add file-backed archive installation kernel (#16421) * 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 * chore(release): sdk-typescript v0.1.28 (#16412) * chore(release): v0.26.8 * chore(release): sdk-typescript v0.1.28 --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Shaojin Wen <[email protected]> * fix(core): keep plain bracket classes in simulated sed Matching `[:`/`[.`/`[=` anywhere in the pattern rejected ordinary classes such as `[.]` -- a common literal-dot idiom that sed and the translator both handle. Only open a POSIX construct when the opening bracket sits inside a bracket expression, so `[^[:space:]]` and `[[=a=]]` stay declined while `s/[.]/X/g` keeps the simulated path. * fix(core): decline an escaped `[` followed by a POSIX-looking class `s/\\[[:alpha:]]/X/g` is a literal `[` and an ordinary class to BSD sed, so the simulation rewrote the file. GNU sed rejects that pattern outright as a malformed bracket expression, so a simulated edit writes a change the user's own sed would have refused. The bracket scanner skipped the escaped `[` and so never saw the nested class. Decline the POSIX-shaped lookalike outside a bracket expression and leave a plain escaped `[`, and an escaped `[` inside a class, on the fast path they already had. --------- Co-authored-by: 肖洪进 <[email protected]> Co-authored-by: Shaojin Wen <[email protected]> Co-authored-by: 2SinceJuly <[email protected]> Co-authored-by: qqqys <[email protected]> Co-authored-by: callmeYe <[email protected]> Co-authored-by: Bob.qwencode <[email protected]> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
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]> |