mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-25 16:44:36 +00:00
633 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0a3d7bb5c1
|
feat(acp): Protect against repeated tool execution failures (#8469)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* feat(acp): protect repeated tool execution failures Add a conservative prompt-local guard for repeated typed ACP tool execution failures, with shadow/warn/enforce rollout modes, privacy-safe telemetry, and coverage for the final execution outcome contract. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(acp): harden repeated tool failure guard rollout Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(acp): address repeated failure guard review Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(acp): improve repeated failure guard recall Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(acp): clarify review and rollout gates --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
a810f7e16c
|
fix(serve): Make session restore timeouts safe and observable (#8691)
* fix(serve): make session restore timeouts safe Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): restore missing core mock exports in the ACP worktree suite The restore-tracing change added `extractDaemonTraceContext` and `withDaemonSpan` to `acpAgent.ts`, but `acpAgent.worktree.test.ts` replaces `@qwen-code/qwen-code-core` with a full mock factory that never listed them. `loadSession` then failed on an undefined export, taking all three cases down and producing teardown rejections from the half-built agent. The sibling suite was updated; this one was missed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(serve): bound and disambiguate the abandoned restore lifecycle Four follow-ups from review of the restore timeout work. A startup budget may now raise the restore budget but never lower it. Taking an explicitly configured `initializeTimeoutMs` as the restore fallback meant a deployment that tightened its child-initialize check still inherited a sub-default restore deadline — exactly the failure this change exists to remove. An explicit `sessionRestoreTimeoutMs` still wins outright, including below the default, for deployments that want restore to fail fast. Validation now names the field actually at fault. A restore fenced behind a timed-out predecessor is no longer reported as an ordinary in-flight restore. It carries `reason: awaiting_abandoned_cleanup` and a retry hint of one restore budget (capped at 120s) instead of the ordinary 5 seconds, because the fence cannot clear until the non-cancellable ACP request settles and a 5-second cadence just spins the caller against a 409 it cannot resolve. Whether a channel is condemned is now derived rather than sticky. A timeout recorded `emptyReapPending` permanently, so any channel that had ever seen one was guaranteed to be reaped once its remaining work drained, forcing a cold respawn even when the late restore had landed and closed cleanly. The reap condition is now computed from an outstanding `unsettledAbandonedRestores` set, quarantine, or an ordinary pending empty reap; real settlement clears the entry and hands the channel back to the configured idle policy. Abandonment no longer retains ownership without bound. One further restore budget after the deadline, a still-unsettled restore marks the channel `restoreSettlementOverdue`: existing sessions and workspace control keep working, but fresh session work is refused so the channel can drain, since closing the transport is the only lever that releases a permanently hung request. Releasing capacity while hidden work runs would allow unbounded oversubscription, and force-killing a channel with live siblings would reintroduce the failure this work removes, so neither is done. Fresh-admission blocking is now scanned across alive channels rather than tracked in a single reference, so a second condemned channel cannot silently displace the first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(serve): keep the abandoned restore lifecycle off ids it no longer owns Two correctness gaps in the abandoned-restore machinery introduced by this PR, both reported by automated review and both confirmed by mutation testing (each new test fails when its fix is reverted). A caller-supplied `sessionId` is used verbatim by the agent, but `spawnOrAttach` never consulted `inFlightRestores`. A fresh spawn could therefore take an id that a restore still owns, in either lifecycle phase. The consequences were silent: `abandonedRestoreIds` suppresses session updates, guardrail events, and child notifications, so the new session would have registered successfully and then emitted nothing; and a late `settleAbandonedRestore` would have closed and tombstoned it out from under its owner. Such a spawn is now rejected with the same `RestoreInProgressError` and reason the restore path uses, so the caller gets the correct retry hint for whichever phase is holding the id. The cleanup path is guarded independently, because the request-level check only covers the id the caller asked for and a session registers under the id the child returns. An abandoned restore never reaches `createSessionEntry` — the deadline rejects before registration — so any live entry under that id belongs to someone else. Cleanup now detects that and returns without closing or tombstoning, releasing its own bookkeeping instead. The notification fence has no TTL and was only cleared by `markRestoreInFlight`, which covers a subsequent restore and nothing else. `createSessionEntry` now clears it for every registration route, so a legitimate owner of the id is never handed a session that silently drops everything the child sends it. Also tightens two tests that could not observe the values they pin. The SDK default restore timeout admitted any value in (30s, 70s]; it is now split at the exact boundary, so collapsing the default onto the 60s server budget — which would make the client abort race the daemon's own deadline and cost the caller its structured 504 — fails. And the advertised-budget propagation from capabilities through to the SDK call had no live-path assertion; dropping the capabilities argument at the real call site left every existing test green. The `as never` casts are replaced with typed `DaemonCapabilities` values so a field rename fails typecheck. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(serve): let a condemned channel drain without its wedged child Merging main's active-work close protocol (#8588) into this PR's abandoned restore bound produced a deadlock that neither side has on its own, and the conflict resolution was committed without running tests. `maybeCloseIdleSession` now routes through `confirmChildUnheld`, which asks the child whether it still holds work before closing a session nobody is attached to. That is right in general and wrong for a channel this PR has already condemned. `restoreSettlementOverdue` and quarantine exist precisely because the child stopped being answerable, and their whole premise is that visible work drains so the channel can be reaped — closing the transport is the only thing that can release a restore we cannot cancel. Making that drain depend on a round trip to the wedged child inverts it: a child stuck in a non-cancellable restore is exactly the one that cannot reply inside `ACTIVE_WORK_CLOSE_TIMEOUT_MS`, so the sessions never close, the channel never drains, the reap never fires, and the bound never takes effect. A channel condemned by the restore lifecycle now skips the round trip and proceeds to local teardown. Nothing is attached to the session by then — `maybeCloseIdleSession` gates on that — and the sibling-safety invariant is untouched: this closes sessions whose clients have already left, it does not force-kill a channel that still has live ones. The regression test drives an overdue channel whose child never answers the close-if-unheld probe and asserts the detach still reaps it. Reverting the guard reproduces the deadlock as a test timeout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(serve): pin the restore-timeout contract the review found unasserted Automated review identified eleven places where the restore-timeout work's behavior was correct but unpinned — each with a mutation that ships green. Every fix below was verified the same way: apply the mutation, watch the new assertion fail, revert, watch it pass. The timeout path's telemetry had no coverage at all, which is the sharpest gap given that observability is what this work exists to deliver. A shared recorder now asserts the public timeout result and its kill_empty-vs- fence_shared signal, the late arrival, and the cleanup outcome for both the closed and quarantined cases. The deadline timer's cancellation on a successful restore was likewise unpinned: deleting both `clearTimeout` calls kept the whole suite green, while in production the stale timer fires one budget after a successful restore and abandons a live session — fencing its frames, closing its event bus, and emitting a spurious timeout. A success-path test now advances past the deadline and asserts no second public result. Three more bridge assertions proved less than they claimed: the concurrent- restore case never checked that the abandoned restore settles, the workspace-control case never checked that the deferred reap eventually fires, and the resolver never pinned the accepting side of the MAX boundary (a `>` to `>=` mutation rejects the largest legal delay at boot). The workspace-control case also needed a positive channel idle budget, since with the default zero the idle-timer kill substitutes for the reap junction under test; its assertions are rewritten around the derived reap semantics rather than the sticky flag they predate. Outside the bridge: the scheduled-task timeout wiring had no test, so deleting the arguments silently fell back to the helpers' own defaults; the cold restore path never asserted that `live_restore_ms` is absent; the SDK's per-request validation and its over-ceiling clamp were untested; the WebUI watchdog test jumped straight to its own value, staying green for any watchdog at or below it, including the 30s attach value that would recreate the original symptom in the browser; and the two new known error types were unexercised, so dropping either would relabel every restore-timeout and quarantine error as unknown. Two review items are deliberately not taken here and are recorded in the design doc's non-goals instead: transcript materialization is still not separately attributable from `config_setup`, which needs instrumentation inside the core session loader that P1/P2 restructures anyway, and sibling event-loop latency during a large restore remains unmeasured. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(serve): bound the condemned-channel close and complete the fence contract Second automated review round, on the code the first round produced. One Critical and twelve suggestions; all verified by mutation before and after. **The Critical is a regression I introduced.** Letting a condemned channel skip the bounded hold probe routed it into `closeSessionImpl`, whose agent close is unbounded when it throws on failure — so the fix traded a bounded wait on a wedged child for an unbounded one. A settlement-overdue channel with an unresponsive child would hang `detachClient` forever, strand the session in `closing`, never drain, never reap, and 503 every new session until restart: strictly worse than before. `CloseSessionOpts` now carries an `agentCloseTimeoutMs` that the condemned path sets, so a hang lands in the existing unknown-outcome recovery, which kills the channel — the teardown the drain was waiting for. The earlier test missed this because its fake child still answered the plain close; it now answers nothing at all, and asserts the detach itself returns. **The fence was invisible on the transports clients actually use.** `toRpcError` had no `RestoreInProgressError` case, so over acp-http and acp-ws — which SDK negotiation prefers over REST — the fence degraded to an opaque internal 500 with no code, reason, or hint, and the backoff contract this work documents was impossible to honor. **Two retry hints still advertised five seconds for states that outlive a budget.** The restore 504 creates the fence, and quarantine lasts until the channel drains; a fresh-id caller never reaches the 409 that carries the real hint, so its header was the only signal it got. Both now derive from the budget through one shared clamp helper, which also replaces the formula that was inlined in the bridge and gives the documented 5-120s bounds a test. **A spawn collision reported an operation the caller never issued**, naming the restore owner's action as both the active and the requested one and telling the caller to retry an endpoint it never called. The rest: five places still described the initialize-timeout fallback as a plain chain rather than raise-only, contradicting sibling docs shipped in this same PR; the design doc omitted the retry-hint clamp; the protocol reference omitted the new spawn emission site; the error taxonomy omitted `restore_settlement_overdue`, which matters because its audience is monitoring. Test-only gaps: the dynamic 409 had no HTTP-layer coverage, the 120-second cap was unpinned, and the SDK's precedence of an explicit global timeout over the advertised budget was pinned only branch-by-branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(serve): preserve restore session ownership handoff Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
bf84caf173
|
feat: add Local Control pairing to CLI and Desktop (#8727)
Some checks failed
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
npm cache producer / Save npm cache (push) Has been cancelled
* feat(cli): add Local Control pairing * fix(cli): address Local Control review feedback * fix(cli): allow Local Control loopback origin * feat(desktop): add Local Control pairing * fix(local-control): bound unauthenticated connections * test(desktop): allow Windows proxy cleanup * test(desktop): avoid socket cleanup timing * fix(desktop): surface Local Control status * fix(desktop): simplify Local Control window * fix(desktop): harden Local Control pairing * fix(desktop): bind Mac wake lock to app |
||
|
|
39377fcff3
|
feat(daemon): add batch skill toggle API (#8664)
* feat(daemon): add batch skill toggle API * test(serve): update capability integration baseline * fix(daemon): apply skill batches atomically * test(daemon): pin Skill batch toggle contracts and fix docs examples * test(daemon): pin Skill batch toggle mutants flagged in review * test(daemon): cover Skill batch toggle edge cases * docs(daemon): clarify Skill batch toggle contract notes from review Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(daemon): pin Skill batch toggle cap semantics and SDK surface shape * test(daemon): pin Skill batch toggle mutants flagged in round-5 review --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
33b124321c
|
feat(core): support Qoder plugin extensions (#8661)
* feat(core): support Qoder plugin extensions * fix(core): address Qoder extension review feedback * fix(core): handle annotated tags and unsafe parse errors * fix(core): harden Qoder conversion edge cases * fix(core): sanitize Qoder conversion inputs * fix(core): address Qoder extension round-3 review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): honor explicit marketplace selection over Qoder manifest * fix(core): preserve nested plugin update provenance --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
bb8f2c0129
|
fix(cli): scrub inherited loader env vars from daemon session subprocesses (#8663)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* fix(cli): scrub inherited loader env vars from daemon session subprocesses Daemon-mode sessions bound to one workspace inherited loader-affecting env vars (NODE_OPTIONS with dev-harness --import hooks, NODE_PATH, preload-class vars) from whatever shell launched the daemon, so subprocesses in another workspace resolved modules through the launching checkout's tree (fixes #8653). Scrub the loader subset of RELOAD_EXCLUDED_KEYS from process.env at the two process boundaries that host sessions: the daemon after freezing its boot env (the frozen copy keeps loader vars so dev-mode ACP children can still boot), and the ACP child after the relaunch/sandbox handoff (the respawned child re-scrubs itself). Fixes #8653 * fix(cli): reject loader env keys in initial .env load and log scrubs A trusted workspace's .env could re-populate the loader-key slots that scrubInheritedLoaderEnv() emptied in the daemon process, because canApplyParsedEnvKey applied RELOAD_EXCLUDED_KEYS only on reloads. Reject the loader subset on every .env application path so one workspace's loader hook cannot reach other workspaces' session subprocesses through the shared daemon env. Also make the scrub return the removed keys and emit a stderr breadcrumb naming them at both boundaries, so a session subprocess missing an inherited var can be traced back to the scrub. * fix(cli): reject loader env keys in serve fast path before env freeze * fix(cli): deny npm_config_node_options and report rejected loader keys Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): match loader env keys case-insensitively and report settings.env rejections * fix(cli): canonicalize loader env key spellings and scrub channel daemon workers npm maps non-leading underscores in npm_config_* keys onto hyphens, so npm_config_node-options injected NODE_OPTIONS exactly like npm_config_node_options while slipping past every loader gate and scrub. Canonicalize case and underscore/hyphen spelling on both sides of the loader-key membership test, covering .env loads, settings.env application, the serve fast path, and the inherited scrubs. Channel daemon workers are spawned with the daemon's pre-scrub base env but are not ACP children, so they never ran the self-scrub; mirror the ACP-child scrub at the worker entry so nothing a worker spawns inherits loader vars into another workspace. Scope the settings.env rejection warning per workspace so a multi-workspace daemon reports every workspace's rejection instead of deduping them all under one label, revert the unread loadServeFastPathEnvironment return value to void, and pin the buildRuntimeEnvironment settings.env gate and the consume-once stash reset with discriminating tests. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): address review findings for loader env denylist - report rejected loader keys through the daemon log after boot (per-workspace .env loads were silent once boot stderr was gone) - accumulate serve fast-path rejected keys across loads instead of overwriting, dedupe, and use normalized env file paths for rejection sources - restore scrubbed inherited loader env vars on embedded runQwenServe close() - add regression tests for ENV scope coverage, reporter dedupe, fast-path accumulation, and post-boot daemon-log diagnostics - scope docs: top-level env rejection does not apply to mcpServers[].env / hooks[].env; document serve loader-scrub behavior * fix(tests): add writeStderrLineSafe to stdioHelpers mocks and sync env guard allowlist * fix(cli): scrub loader vars from the daemon base env and retighten the denylist The frozen daemonRuntimeBaseEnv was captured before the launch-env scrub, so daemon-spawned session processes still booted under the inherited loader — the child-side post-boot scrub ran after Node had already consumed NODE_OPTIONS. The base env is now scrubbed before the freeze (except under the DEV=true harness, whose .ts entries need the tsx loader), and close() restores the host's launch env from a pristine snapshot. Denylist scope now follows the injection-vs-search-path split: adds the npm config-file redirect keys, ZDOTDIR, and a BASH_FUNC_* prefix rule; moves ENV/LD_LIBRARY_PATH/DYLD_LIBRARY_PATH back to their reload-only tier (mainstream toolchain compatibility); blocks QWEN_CLI_ENTRY and NODE_EXTRA_CA_CERTS from project .env files. The ACP-child scrub is gated on the daemon stamp (QWEN_CODE_SERVE) so direct editor ACP integrations keep the user's exported environment, and the daemon's per-workspace .env rejections are now reported from buildRuntimeEnvironment. * fix(cli): block DEV spoofing, case-insensitive env exclusions, serve boot env restore (#8663) Address round-6 review: DEV joins the hardcoded project-env exclusions so a workspace file cannot disable the daemon's loader-env scrub; the hardcoded tier is enforced case-insensitively (Windows env lookup is case-insensitive) via isHardcodedProjectEnvExclusion at every application gate; runQwenServe's catch restores the scrubbed launch env and detaches the rejection reporter when startup fails after the scrub. Tests gain the matching regressions, home-env hermeticity, source-scoped warning filters, and tmpdir cleanup; the unreachable reload delete-pass loader guard and its vacuous test are removed. * fix(cli): match the reload-excluded env tier case-insensitively too Round-6 follow-up: R6-3 named RELOAD_EXCLUDED_KEYS.has() among the gates a case variant slips, but the hardcoded-tier fix left the reload-only keys (QWEN_SERVER_TOKEN, PATH, HOME, TMPDIR, …) on exact-case matching. On Windows a lowercase twin names the same OS variable, so a mid-session settings.env/.env edit could still rotate the daemon token or move PATH through a case respelling. Fold the reload tier the same way and pin it with a reload-behavior regression test. Also note DEV in the settings.md exclusion docs. * test(cli): redirect HOME in environment.test.ts for full home-env hermeticity The source-scoped warning filters fixed the warning-count assertions, but the process.env assertions (e.g. 'never applies entrypoint or trust-anchor keys') still read state a real home .env can pollute: home scope deliberately bypasses the hardcoded exclusions, so a dev machine with QWEN_CLI_ENTRY in ~/.env applies it and fails the test while CI stays green. Redirect HOME/USERPROFILE to an empty temp dir in beforeEach — verified by running the suite with HOME pointed at a poisoned home. --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
fd76d4ddde
|
fix(cli): let ESC cancel ongoing work before popping queued messages (#8353)
* fix(cli): let ESC cancel ongoing work before popping queued messages When the agent is actively responding (streamingState === Responding), InputPrompt's ESC handler consumed the key before AppContainer's global cancel-work handler could fire. Users had to press ESC 3 times (pop queue, clear input, cancel work) to stop the agent. Skip the pop-queue-into-input and double-ESC-clear logic when the agent is responding, returning false so the key propagates to the global handler which cancels the ongoing request. The up-arrow key still pops queued messages into the input at any time. Fixes #8201 * fix: narrow ESC fall-through to empty buffer + add regression tests Address wenshao's review on #8353: - Gate the return false on buffer.text === '' to prevent BaseTextInput's default ESC from silently wiping typed input without double-press confirmation - Add resetEscapeState() before return false to clear any pending escPressCount/escape-prompt timer - Add two regression tests with streamingState: StreamingState.Responding: 1. queue non-empty + ESC -> popAllQueuedMessages NOT called 2. buffer has text + single ESC -> buffer NOT cleared * fix: correct ESC comment to reflect KeypressContext broadcast model Address bot suggestion: the comment claimed returning true 'consumed the key before the global cancel-work handler could fire', but KeypressContext broadcasts to all handlers regardless of return value. The real mechanism is that popQueueIntoInput() fills the shared buffer, steering AppContainer's handler into its 'input has content -> double-press to clear' branch instead of the cancel-work branch. * fix: correct comment to accurately describe return false -> BaseTextInput fall-through Bot suggestion: the comment said returning false 'avoids' BaseTextInput's wipe, but return false actually *enables* it (BaseTextInput only short-circuits on truthy returns). The buffer is safe because the buffer.text === '' gate makes the wipe a no-op, not because return false prevents it. Reworded to make this explicit and warn against relaxing the gate. * test(ui): add positive AppContainer ESC cancel regression test The PR's Responding guards were pinned only by InputPrompt-side tests (queue not popped; non-empty buffer preserved). Add the positive case the review asked for: while Responding with an empty buffer and queued follow-ups, a single Esc reaches the global handler's cancel-work branch (cancelOngoingRequest called once) and the queue is not consumed. #8201 * test(ui): clarify ESC cancel test scope vs end-to-end drain The positive ESC cancel test asserts popAllMessages is not called, but the comment framed it as 'must not consume the queue' end-to-end. In production that exact cancel path DOES drain the queue back into the buffer via the cancel handler (cancelOngoingRequest -> onCancelSubmit -> popAllMessages), under the 'never silently drop queued work' invariant. The assertion only holds because cancelOngoingRequest is replaced by a spy here, severing that hop. Reword the comment to describe the real contract: the global keypress handler itself doesn't pop the queue (InputPrompt owns that and skips it while Responding; #8201), while the end-to-end drain is a separate hop severed by the spy. Addresses the review finding on AppContainer.test.tsx:2405. * test(ui): pin ESC return-false branch and dedupe getGlobalKeypress Address two review suggestions on the ESC cancel tests: - The `return false` branch in InputPrompt.tsx (Responding + empty buffer + empty queue -> defer to AppContainer's cancel-work branch) had no test coverage: reverting it left all 334 tests green. Add an InputPrompt test that pins it (no queue pop, no buffer mutation). - The AppContainer cancel test inlined a byte-identical copy of the getGlobalKeypress() helper that already existed ~2600 lines down in the Ctrl+O describe block. Hoist the helper to the outer describe so both blocks share one definition of the fragile toString() discovery idiom. * test(ui): escape raw ESC byte in cancel test fixture Per review (R4-1): the sequence literal embedded a raw 0x1B control byte that renders as an empty string in diffs and truncates grep output, so the fixture was unreadable. Use the escaped form matching the sibling vim-INSERT fixture on the line above. * test(ui): assert buffer stays empty on ESC cancel + fix test comment Per review (R5-1/R5-2): the flagship #8201 test asserted only the mechanism (popAllQueuedMessages not called), not the effect (buffer stays empty so AppContainer takes its cancel branch). Add the buffer assertion. Also correct the return-false test comment: deleting that branch leaves the test green (KeypressContext.broadcast ignores return values, BaseTextInput's clear is a no-op on empty buffer), so the test pins the no-side-effect contract, not the branch itself. * test(ui): cover queue+text ESC and double-ESC-clear while responding Per review (R6-1/R6-2): the pop-skip guard was pinned only for the empty-buffer case, and the double-press clear contract had no test under Responding. Add: - non-empty queue AND typed text: ESC does not pop the queue and preserves the buffer (pins the guard regardless of buffer content). - double-ESC while Responding: first ESC preserves typed text, second clears it (pins the double-press contract this diff preserves). * test(ui): dedupe escKey fixture and tighten double-ESC timing Per review (R7-1/R7-2): the double-ESC test spaced presses with the default 150ms wait (~30% of the 500ms window); use 50ms to match the sibling double-ESC test. Hoist the escKey fixture to the Cancel Handler describe scope so both tests share one definition (matching the getGlobalKeypress hoist this PR already did). * test(ui): add missing removeGoalTurns to cancel-handler queue mock Per review: the cancel-handler test's useMessageQueue mock omitted removeGoalTurns, a required member that every other queue-mock override in this file includes. The real cancel handler calls removeGoalTurns() before popAllMessages(); the test passed only because cancelOngoingRequest was a spy severing that hop. * test(ui): reuse getGlobalKeypress in vim-INSERT cancel test Per review (R9-1): the vim-INSERT test still inlined a handler-discovery loop matching on 'handleExit', duplicating the hoisted getGlobalKeypress helper (matching TOGGLE_THINKING_EXPANDED). Both tokens occur in the same handleGlobalKeypress closure, so the two idioms can only drift. Reuse the shared helper. * test+docs(ui): escape ESC byte in shared fixture and document Responding Esc Per review (R10-1/R10-2): the shared escKey fixture embedded a raw 0x1B control byte (invisible in diffs, truncates grep). Use the escaped form. Also update keyboard-shortcuts.md: Esc now cancels the ongoing request while the agent is responding instead of moving queued messages back into the input. * docs(ui): correct ESC/Up-Arrow queue-pop description to match code Per review (R11-1): the previous wording said queue pop happens only when idle, but Up Arrow pops in any state (no streamingState guard) and Esc pops whenever not actively responding (including WaitingForConfirmation). Reword to match the code, and note that the responding-cancel only fires when the input is empty. * refactor(ui): drop dead Responding ESC guard per maintainer review wenshao's mutation test showed guard #2 (Responding + empty buffer -> return false) is dead code: with it gone, control falls to the escPressCount===0 branch which returns true on an empty buffer, and KeypressContext.broadcast ignores handler return values anyway - AppContainer's own cancel branch acts on the empty buffer either way. Remove it, fold the subscription-ordering invariants it relied on into guard #1's comment, and document that only Responding is gated (do not broaden to !== Idle or ESC becomes a no-op during a tool confirmation). Also tighten the docs wording: cancelled queued messages are moved back into the input, not preserved. #8201 * docs(ui): correct four review comments in ESC cancel path Round-13 review (no blockers) flagged comment inaccuracies that could misdirect future debugging: - R13-1: the invariant comment pointed at an integration test that does not exist - note the harnesses mock each other's side instead. - R13-2: the regression-test comment still said the branch returns false and that AppContainer acts on return values; it returns true and broadcast ignores return values. - R13-4: the docs row claimed Up Arrow/Esc pop in any state, but during WaitingForConfirmation Composer unmounts InputPrompt (isInputActive admits only Idle/Responding), so neither key pops. - R13-5: the guard comment warned against broadening to !== Idle as if WFC ran this branch; it never does because the component is unmounted. No behavior change. #8201 * docs(ui): correct subscription-order and double-ESC-cancel comments Round-14 review: the invariant comment overstated subscription order as load-bearing - the Responding pop guard skips the pop in either order, and InputPrompt re-subscribes after AppContainer on any remount (e.g. a tool-confirmation round trip), so only the buffer.text-liveness invariant matters. And the double-ESC clear comment now notes it composes with AppContainer's cancel on the same keypress in the initial order but lands on the next press after a remount. No behavior change. #8201 --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
88a325bce9
|
feat(workflows): add cooperative pause and resume (#8320)
* feat(workflows): add cooperative pause and resume * fix(workflows): restrict pause to background runs * fix(cli): clarify foreground workflow pause errors * fix(core): preserve dispatch errors across cancellation * test(core): cover late workflow state callbacks * fix(workflows): address review suggestions (#8320) - Rename misleading `terminal` local to `presentation` in BackgroundTasksDialog - Fix vacuous `toContain('p')` assertion to `toContain('Background tasks + p')` - Fix vacuous gate assertion with macrotask yield in scheduler test - Add over-count cap test for `onAgentCompleted` past dispatched count - Add pausing-state approval parking test - Remove dead `concurrencyLimiter` module (no production consumers) * test(workflows): pin review-flagged mutation-surviving branches (#8320) * test(cli): use valid agent status in detail-view reset test (#8320) * test(workflows): harden pause-gate settle probes with a full flush (#8320) * fix(workflows): address round-5 review findings (#8320) * test(ci): sync review timeout assertions with repository variables (#8320) * fix(workflows): address round-6 review findings (#8320) * fix(workflows): address round-7 review findings (#8320) * fix(workflows): address round-8 review findings (#8320) * fix(workflows): address round-9 review findings (#8320) * fix(workflows): address round-10 review findings (#8320) * fix(workflows): address round-11 review findings (#8320) * fix(workflows): address round-12 review findings (#8320) * fix(workflows): address round-13 review findings (#8320) * fix(workflows): address round-14 review findings (#8320) * fix(workflows): address round-15 review findings (#8320) --------- Co-authored-by: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com> Co-authored-by: qwen-code-bot <qwen-code-bot@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
b34a08d16f
|
fix(core): separate hook context from transcript display (#7948)
* fix(core): separate hook context from transcript display * test(ci): gate desktop transcript projection * revert: keep desktop CI scope unchanged * test: cover transcript display fallbacks * fix(transcript): address review feedback * fix(transcript): reconcile post-merge provenance paths * fix(webui): preserve legacy transcript concatenation * test(transcript): cover projection consumers * fix(transcript): consolidate hook context projection * fix(transcript): support single-field display provenance * fix(transcript): strip hook context with invalid metadata * test(acp): cover empty replay display text --------- Co-authored-by: destire-mio <248462155+destire-mio@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
c2026882b7
|
fix(acp): emit context usage updates (#8513) (#8528)
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
26352fcc6a
|
feat(external-context): Add optional Mem0 memory writes (#8507)
* feat(external-context): Add optional Mem0 memory writes Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(hooks): Preserve confirmation content visibility Render PreToolUse confirmation reasons literally and keep long confirmations accessible through the virtualized TUI. Add unit and interactive regression coverage for Mem0 write confirmations. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(external-context): Address memory write review findings Align Hook and MCP argument handling, distinguish definitive Provider rejections from ambiguous outcomes, improve deployment diagnostics, and document the write-back trust boundary. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(hooks): Refine plain-text confirmations Render URLs consistently, avoid persistent virtual viewport gaps, and document the literal-rendering and managed deployment boundaries. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(external-context): Support Auto Edit write confirmation Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(external-context): Harden write confirmations Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): Measure virtual row height directly Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): Preserve YOLO Hook confirmation content Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
028747aa41
|
feat(feishu): enrich observed contact labels (#8569)
* docs: design feishu observed contact enrichment * docs: add Chinese Feishu enrichment design * feat(feishu): enrich observed contact labels * fix(feishu): preserve enriched contact labels * fix(feishu): harden observed-contact label enrichment lifecycle * fix(feishu): bound label caches, honor observation recency, silence enrichment token failures (#8569) - hydrate runtime label caches from the newest observation per contact so stale group membership labels cannot overwrite more recent ones - cap the user/chat label, in-flight lookup, and write-dedup maps at 500 entries (matching the persisted registry) and evict oldest entries - route best-effort label lookups through a silent token refresh path so enrichment failures no longer write to stderr - add tests for silent token refresh, newest-label hydration, cache cap, and the persisted-observation reject path in hook ordering * fix(feishu): address observed-contact label review feedback (#8569) * Track core (non-silent) waiters on the shared tenant-token refresh so a silent-initiated refresh still logs token errors for joined delivery callers. * Short-circuit label lookups on the resolved names cache so evicted lookup entries do not trigger redundant API requests. * Re-hydrate label caches from the persisted registry after an in-lifetime cache eviction so the next initial write cannot clobber a persisted label with the raw ID. * Add mutation-proof regression tests for the channel-isolation filter, the list-failure swallow, the silent HTTP-error branch, and the 'unknown' label guard. --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com> |
||
|
|
b40719a8bb
|
fix(cli): Run ACP agent fan-outs concurrently and past the tool-call cap (#8631)
* fix(cli): Run ACP agent fan-outs concurrently and past the tool-call cap The daemon's ACP session executed tool batches differently from the core scheduler in two ways that broke long agent fan-outs such as /review: runBounded — the runner for concurrent batches — forced the first three calls of any batch larger than the invalid-params threshold to run one at a time, then clamped the rest to concurrency 3, although agent calls are concurrency-safe and core's runConcurrently runs them at QWEN_CODE_MAX_TOOL_CONCURRENCY (default 10). A /review fan-out of 10-15 agents therefore ran almost serially. Agent-only batches now skip the serial prefix and the clamp: an invalid agent call fails in build() before any side effect, so the concurrent loop's near-threshold check still catches invalid-params loops just as fast. The per-turn tool-call cap halted unconditionally at model.maxToolCallsPerTurn (default 100) while core's LoopDetectionService treats the default as adaptive — past the soft cap a productive turn (diverse calls, no repetition) continues until a stuck-repetition signal or the hard backstop (soft cap x 10). A /review orchestrator needs well over 100 calls, so every high-effort review under qwen serve died mid-review at call 101. The daemon now mirrors core's checkTurnToolCallCap semantics, reusing the same thresholds. Measured on two high-effort /review runs (PRs #8522 and #8529): the baseline died at call 101 after ~8.3h each; after this fix both reviews run to completion in 4.4h / 5.3h, first fan-out wave 85m -> 33m, reverse-audit rounds 63-86m -> 25-35m. * fix(cli): regenerate settings schema after maxToolCallsPerTurn doc update (#8631) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): Gate the daemon repeat halt on skipLoopDetection like core * fix(cli): Address ACP fan-out review: keep wide-batch results, shared cap predicate (#8631) - runBounded no longer aborts in-flight calls when loop detection fires in the capped race branch: wide batches keep in-flight results and only skip the unstarted tail, matching narrow-batch behaviour (nothing executed is discarded either way). - Extract shouldHaltOnTurnToolCallCap from core's checkTurnToolCallCap and call it from the daemon guard so the two runtimes share one halt predicate and cannot drift. - Hoist canonicalToolName into tools/tool-names.ts beside ToolNamesMigration; scheduler, loop detection, plan redaction and memory refresh now share the single alias resolver. - Correct the wrong-direction cap wording (the daemon undershoots an explicit cap / hard backstop — the batch check runs before execution; the adaptive soft cap is exceeded by design up to the backstop) in the daemon comment, settingsSchema.ts (schema regenerated) and settings.md, and scope the always-on-guard sentence to core-client sessions. - Tests: adaptive hard backstop, wide-batch loop tail skip, wide-batch keep-results, provider-duplicate counter exclusion, `task`-alias fan-out, getToolCallRepeatKey alias/key-order coverage; raise the fan-out concurrency deadline off the 2s wall clock. * fix(cli): Address review: complete loop-guard docs, pin test envs, drop dead export (#8631) * fix(cli): Address review: correct parity comment, pin halt semantics, test cross-response repeats (#8631) --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
edb420393e
|
fix(channels): manage DingTalk interactive card config (#8517)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* fix(channels): manage DingTalk interactive card config * test(cli): cover nested channel object validation * fix(channels): harden nested management metadata * fix(channels): isolate invalid management descriptors * fix(channels): isolate invalid management metadata from channel runtime * fix(channels): reject reserved unknown keys in management config upserts * fix(channels): harden channel management validation and editor checks Reject management descriptors that lack a fields array at registration so broken plugins are stripped to unmanageable instead of being advertised as manageable and failing every upsert with an unmapped TypeError. Reserve the top-level "type" field key and require enum fields to declare at least one option, both of which the settings store could never accept. Treat whitespace-only number drafts as empty in the channel editor, consistent with the module's other emptiness checks. Also give the SDK descriptor mirror test a runtime wire-shape walk over the built-in catalog, add the parser's timeout rejection boundary, and restore the exact built-in catalog membership assertion. * fix(channels): validate management field shapes and editor bounds (#8517) * fix(channels): align management validation layers and pin gate behavior (#8517) Read envResolvable by truthiness in the settings store so it matches the registration gate and the editor, instead of rejecting the advertised environment references of untyped plugins. Fail closed at registration on non-finite exclusiveMinimum values, empty object property lists, and async validateConfig functions, all of which would otherwise advertise a field or save path that can never succeed. Strip invalid management metadata over a prototype-preserving copy so class-instance plugins keep their createChannel implementation. Move the unchanged-value preservation exemption ahead of the object shape rejection so a stored non-record value (for example a hand-written null) no longer locks every unrelated management edit of that channel. Clamp DingTalk question-card timeouts at the maximum setTimeout delay, since Node treats larger delays as one millisecond and would expire cards instantly. Pin the previously untested load-bearing behaviors: per-key previous threading in the recursive validation, the preservation exemption's precedence over nested required enforcement, nested "type" properties, depth-2 nesting rules, and the nested-only constraints of the daemon descriptor wire contract. * fix(channels): close reserved-key preservation gaps and pin gate behavior (#8517) * test(cli): tolerate IPv6-less hosts in serve ::1 bind tests (#8517) The self-hosted CI containers can have no IPv6 loopback, where the two runQwenServe tests that bind ::1 fail with EADDRNOTAVAIL. Probe the interfaces once and skip only the IPv6-dependent binds there; every assertion still runs on IPv6-capable hosts. * fix(channels): align descriptor type contracts with runtime validation (#8517) The registry already rejects object fields without a non-empty properties array and enums without unique options, but the descriptor types still admitted both, so TS-authored plugins only learned about it when registration stripped their management surface. Make `properties` required, give enums a dedicated descriptor member with required `options`, and drop the never-honored `envResolvable` flag from number descriptors, in both channel-base and the SDK mirror, and export the descriptor sub-types through the webui barrels. Also map a throwing `validateConfig` to the usual invalid-config error and pin the store contracts that had no distinguishing tests: omitting a parent object drops the stored object without checking its nested required, writes replace nested values wholesale, unchanged stored scalars are still re-validated, and valid plugins register by original reference. * fix(channels): defuse validateConfig rejection leak and close descriptor gate gaps (#8517) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
3edecac116
|
feat(channels): support group pairing (#8440)
* feat(channels): support group pairing * fix(channels): address group pairing review * fix(web-shell): show group pairing management * fix(channels): recheck group pairing before history backfill * test(channels): verify group approval isolation * fix(channels): address group pairing review findings and pin behaviors (#8440) - Grandfather the group allowlist file in PairingStore legacy migration - Offer the pairing groupPolicy option in github/gitlab descriptors - Re-export DaemonChannelPairingSubject from the webui barrels - Refresh the GroupGate doc comment and channel docs rows - Pin the unpinned group pairing behaviors called out in review: subject dedup, trigger matrix, notification content/cap/failure/ thread routing, DM negative space under groupPolicy pairing, stored DM loop authz, pairing-enabled guard negative space, approval/revocation HTTP bodies, descriptor-driven gate branch, and the web-shell group approval mirrors - Add a compile-time assertion for the revocation request union * fix(channels): address group pairing review findings (#8440) - Accept 'pairing' in the GitLab connect warning, descriptor help text, and gitlab.md: todos dispatch after one-time group approval. - Model group approvals in the web-shell e2e mock daemon (approve by subject type, GET returns senderIds+groupIds, DELETE accepts groupId) and exercise the group pairing flow in the channels spec. - Add 'pairing' to the groupPolicy enumerations in the plugins and per-channel docs (telegram, feishu, dingtalk, qqbot, wecom). - Update the channel pairing CLI help to cover group requests. - Cap pending pairing requests at one per sender so a single member cannot occupy every shared pending slot. * fix(channels): address group pairing review findings round 7 (#8440) * fix(channels): address group pairing review findings round 8 (#8440) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(channels): address group pairing review findings round 9 (#8440) --------- Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
bf3abdee81
|
fix(serve): Allow approved same-host text reads outside workspace (#8620)
Some checks failed
npm cache producer / Save npm cache (push) Has been cancelled
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* fix(serve): allow same-host daemon text reads Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(serve): address review on same-host text reads Record what the read capability does not fix: #8618 still reproduces for the write and edit family, whose delegated writes are refused after the user has already approved the diff. Give the daemon's pre-approval SSE fan-out its own bullet in the user-facing security section, restore the sentence stating that environment isolation is not an OS security boundary, and make the design doc the single owner of the tradeoff list so tuning a limit cannot leave stale copies behind. Test fixtures no longer land in the developer's real home directory, the assertion pinned to localized rejection copy is dropped, and the combined capability case is split so deleting the write half cannot silently remove read coverage. * fix(test): declare REPO_ROOT and bind the external-read session to the daemon's workspace The external-read regression test referenced REPO_ROOT twice without declaring it, which made it unrunnable everywhere: - On a developer box the ReferenceError was swallowed by the bare catch in findExternalReadBase(), every candidate was discarded, and the test reported a green skip -- exactly the silently-disabled security test the CI loud-fail added last round was meant to prevent. The guard was defeated three lines above itself. - On CI that loud-fail branch threw at module scope, so the file failed to collect and took the four pre-existing tests down with it. Declare REPO_ROOT the way every other daemon integration test does. The session also asked for `workspaceCwd: REPO_ROOT` while beforeAll binds the daemon with `--workspace workspaceDir`, so the create returned 400 Workspace mismatch even once the constant existed. The read under test is external because externalReadDir sits outside the bound workspace, not because the session claims a wider one. Finally, collect each candidate's rejection reason instead of dropping it, and fold it into both branches: the CI throw names why every candidate failed and the developer-box skip warns with the same text. A bare catch cannot tell "no /var/tmp on this image" from a bug in the function, and the second reads as a green skip. Reported by @wenshao, who reproduced all three consequences against a real qwen serve daemon on Linux and supplied the repair. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
2eb5cd6df5
|
feat(serve): observe daemon and child memory against real denominators (#8423)
* feat(serve): observe daemon memory pressure against a real denominator The daemon samples its own RSS and heap but has nothing to divide them by, so nothing in `/daemon/status` says whether a figure is fine or nearly fatal. #8245 landed the denominator (`limits.memory`); this turns it into a reading. `runtime.memory.pressure` reports `level`, `ratio`, `source`, and the six raw figures behind them. The level is the worse of two independent ratios, because the two failure modes are independent: a container dies by RSS against its cgroup limit, while a process on a large host can exhaust V8's heap long before RSS is a meaningful fraction of the machine. Reporting only one hides whichever failure the deployment is actually heading for. `source` names which ratio produced the level, and `unknown` says the daemon could not measure itself — which a consumer must not read as healthy. The denominator is `availableMemoryMb`, not `effectiveBudgetMb`: pressure asks how close this process is to being killed, and what kills it is the cgroup limit or host memory. An operator's budget is a policy number, so classifying against it would report `critical` for a daemon in no danger. `--memory-pressure-mode` is `off | observe`, default `observe`. Both modes report every figure; only `observe` also raises the `daemon_memory_pressure` warning, so `off` leaves the top-level `status` rollup untouched — the thresholds are inherited from an interactive-CLI monitor and are not yet calibrated for a long-running daemon, and a deployment that alerts on `status` needs the reading without the verdict. There is deliberately no `enforce`: nothing here remediates, and a value a caller can pass but never use is a dead switch. It arrives with the enforcement. Scope is the daemon root process only. `childRssCoverage` still reads `primary_only` and says so on the wire; aggregate child RSS and channel workers are separate measurements and land separately. Severity is `warning` at every level including `critical`, because `error` would make `rollupStatus` return `error` for the whole daemon — too strong a claim to stake on uncalibrated thresholds. Refs #8051. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(serve): report aggregate ACP child RSS, not just the primary's (#8462) * test(serve): close the under-determined assertions review probed The automated review mutation-probed this diff and found several assertions that were live but under-determined — each mutant it names kept the whole suite green. All confirmed locally, and all now fail: - Deleting `level !== 'normal'` from the issue gate raised daemon_memory_pressure on a healthy daemon and flipped top-level status to warning on every response — the exact false positive `--memory-pressure-mode off` exists to opt out of. Now covered on both sides: nothing raised at a realistic denominator, exactly one warning at a denominator sized to land this process in `soft`. - Summing children over `list()` instead of `listManaged()` dropped a draining-but-process-holding workspace while `activeAcpChildren` still counted it. The draining bridge now reports RSS, so the byte count can only come from that child. - The message's denominator ternary had no coverage; inverting it sent an operator hunting RSS growth during a heap-driven incident. - A truthiness guard on `ageMs` turned a measured-fresh reading (age exactly 0, when a status read lands in the sampler's millisecond) into `null`, which the field's own docs say never means fresh. - The multi-contributor age test listed ages ascending, so a plain-overwrite accumulator produced the same answer as Math.max. Reordered descending, which kills last-wins and first-wins both. Two declaration-only hunks — the issue-code union member and the `pressure` field — were guarded by tsc alone, which vitest does not run. Both are now pinned at runtime by asserting the code string and the full key set. Also fixes a real display defect: `toFixed(0)` renders a ratio of 0.795 as "hard at 80%", and 80% is critical's documented threshold. One decimal, so the number and the level cannot contradict each other. And corrects a JSDoc claim of mine that was simply wrong: `pressure` is absent not only for direct-embed but on the bootstrap /daemon/status route, which omits runtime.memory wholesale even though the budget is resolved — and that window is not just startup, since a daemon whose runtime fails to start serves the bootstrap app for its lifetime. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(serve): model a per-child heap partition of the daemon budget (#8508) * feat(serve): add the child-heap admission primitives, unwired Groundwork for #8182 step 2. Nothing calls any of this yet, so no child is sized differently and no spawn is refused. `ProcessRegistry.committedProcessCount` counts attached children plus reservations that have not attached. That is the figure admission has to key on: `reserve()` inserts its token synchronously before `spawn()`, so two racing spawns each see the other, while neither appears in `activeProcessCount` until its child attaches. A child leaves the count on exit rather than when `terminate()` starts, so a channel swap counts twice while the old process winds down — deliberate, since its memory is still resident. `getAcpMemoryArgs(explicitMb?)` takes an optional share that bypasses both the module cache and the raise-only guard. Both bypasses are load-bearing. The cache, because the share depends on how many children are live now rather than on the host. The guard, because a budget-derived share is normally *below* the daemon's own heap limit, so routing it through `targetMB > currentLimitMB` would drop the flag, silently restore the overcommit, and leave every test green — the trap against a multi-GB runner, and mutation-checking it by reinstating the guard fails two tests. `createChildHeapPolicy` holds the mode, the budget, and the would-be refusal counter, and answers `decide(concurrentChildren)`. The refusal is derived from the unclamped quotient, not from `recommendedChildShareMb`, because that function clamps *up* to the 512 MB floor: past the point where the pool stops covering the count its answer saturates and can no longer distinguish "barely does not fit" from "wildly does not fit". `ChildHeapPoolExhaustedError` with both transport mappings — REST 503 with Retry-After, ACP `child_heap_pool_exhausted` — added together, since the two mappings are hand-written and drift silently otherwise. Refusing at spawn rather than at registration is the correction #8182 demands: registration allocates nothing, so this surfaces as "no new session in this workspace right now", which is true and retryable. Refs #8182. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(serve): size each ACP child by concurrently live children Wires the primitives from the previous commit into the spawn path, behind `--child-heap-mode off | observe | enforce`, default `observe`. Under `enforce` a child's `--max-old-space-size` is a share of the child pool divided by the children concurrently committed at the moment it spawns — read from the shared ProcessRegistry after `reserve()`, so two racing spawns each see the other. When the pool cannot cover another child at the 512 MB floor the spawn is refused with ChildHeapPoolExhaustedError, which is what turns a per-child ceiling into an aggregate bound: concurrent children can never exceed pool/512. Keyed on concurrency, never on registrations. A dormant workspace has no child, so it costs nothing — the specific correction #8182 records against the withdrawn proposal, which would have shrunk a lone live child to 614 MB because of 24 idle registrations. Default `observe` computes the share and the admission decision and applies neither, counting the refusals that would have happened. The divisor has never been checked against a real multi-workspace deployment, and a non-zero count is how an operator learns enforcement would have broken them without being broken. It also catches the case worth worrying about: a channel swap counts the dying child alongside its replacement, so on a saturated pool enforcement could refuse a restart and leave that workspace with no child at all. Excluding terminating children would authorise real overcommit to dodge a hypothetical refusal, so the count reports it instead. Ceilings already granted are not revisited — V8 cannot lower them — so granted ceilings transiently exceed the pool. Acceptable: the flag is a ceiling, not a reservation, and a workspace with no live sessions has no child and picks up the current share on its next spawn. `limits.memory.enforced` stops being a required literal `false`. #8245 made it one so a client could never mistake that namespace for enforcement that had not shipped; it has now, so the field is a boolean derived from the mode — and stays `false` under `observe`, which applies nothing. Refs #8182. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(serve): correct the claims child-heap enforcement makes false Two sentences in the protocol doc described the memory section as unconditionally observational: "a required `enforced: false`", and "no child spawn argument derives from these values, and no request is refused on their basis". Both are false under `--child-heap-mode enforce`, so both are rewritten rather than left to rot — `enforced` is now documented as the boolean that answers exactly this, and the refusal is documented with its wire shape on both transports. Also documents `childHeap.refusals` as the calibration signal, since a would-be-refusal count is useless if operators do not know to read it before switching to `enforce`; the flag row in the three operator docs; and the design doc's Part 1, which listed applying a share as a compatibility risk without recording how that was resolved. The end-to-end test asserts the policy reaches a real booted daemon's status with `enforced: false` under the default mode — the wire type in that test is a hand-written mirror, so its `enforced: false` literal had to widen too, which is the check that caught the type not being widened everywhere. Refs #8182. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(serve): cover both branches of the enforced tripwire `enforced` was only ever asserted false — the unit tests build no policy and the end-to-end daemon runs the default `observe` mode, so the branch that makes the field worth having was untested. Hardcoding it back to `false` passed everything. Also pins `childHeap: null` as distinct from a policy in `off` mode: the first says no policy exists (direct-embed, or the bootstrap window before the runtime is built), the second says one exists and computes nothing. Refs #8182. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(serve): partition the child pool so granted ceilings stay inside it Review was right that the previous design did not deliver the aggregate bound it claimed. Sizing each child by the count live at *its* spawn bounds the child count but not the memory: V8 cannot lower a running child's ceiling, so grants accumulate as P + P/2 + P/3 + ... = P x H(n). Reproduced exactly — 9557 MB authorised against a 3687 MB pool at seven children on an 8 GB host, and 61355 MB against 15360 MB at the limit on 32 GB. That is 2.6x and 4x the pool, which is what the policy exists to prevent. Grant accounting alone does not fix it: the first child would take the whole pool and the second would be refused immediately. Keeping the invariant requires early children not to receive the whole pool, so the ceiling is now a fixed partition — childPoolMb / maxConcurrentChildren, constant for every child, with maxConcurrentChildren itself derived from the pool and capped at MAX_DAEMON_WORKSPACES. The sum is then n x ceiling <= pool by construction, with no ledger of outstanding grants and no dependence on arrival order. Tested as an invariant across four host sizes: fill the daemon to its admission limit and the authorised total still fits. The cost is deliberate and now documented rather than hidden: a lone workspace on a 32 GB host gets 614 MB rather than the pool, because any child may still be running when the house fills. An 8 GB host admits seven concurrent children at 526 MB each. Also from review: - The policy is no longer built for an injected `deps.bridge`. That bridge carries its own channel and never reaches the factory the policy rides on, so status could report `enforced: true` while nothing was being sized. - Both transport mappings now have direct tests. They are hand-written beside each other and drift silently; the spawn-policy tests cannot catch a wire regression. - Swept the "does not size any child" claim, which enforce makes false, out of the CLI help text, ServeOptions docs, the two operator tables, and the e2e header comment. The 17-configuration table realigns wholesale because that cell was its widest — whitespace only. Refs #8182. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(serve): model the child heap partition, defer applying it Review established that the refusal counter cannot tell an operator whether enforcement is safe, and that is the ground the enforcing mode stood on. While observing, children run on the host-derived ceiling (16384 MB on a 32 GB host), so a workload needing 2 GB of old space is healthy with zero refusals and OOMs the moment a 614 MB partition is applied. The counter measures admission pressure, not ceiling adequacy. Rather than ship a switch with no safe way to decide when to turn it on, `enforce` is removed. `--child-heap-mode` is `off | observe`, and the mode that would apply the partition arrives with the measurement that justifies it: peak old-space per child, compared against the modeled ceiling. That is a real measurement chain — the child reports rss and cpu today, and `--max-old-space-size` bounds old space specifically, so neither rss nor heapUsed answers the question. With nothing applying the partition, the machinery that existed only to apply it goes too rather than shipping unreachable: `getAcpMemoryArgs(explicitMb?)`, `ChildHeapPoolExhaustedError` and both transport mappings, and `limits.memory.enforced` reverts to the required literal `false` it was before. The spawn path is untouched again; the factory asks the policy what it would decide purely so the count is real. Also fixes the zero-pool defect review found, which the removed clamp caused: forcing at least one admissible child on a 512 MB host — where the root reserve consumes the whole 256 MB budget — produced a ceiling of 0, and `--max-old-space-size=0` is V8's *default* heap, not a zero ceiling. A pool that cannot cover one child at the floor now reports `maxConcurrentChildren: 0` and `perChildCeilingMb: null`, and the test that enshrined the old behaviour is inverted. Status now publishes `maxConcurrentChildren` and `perChildCeilingMb`, so an operator can judge the partition against their own workload — the substitute for a counter that cannot judge it for them. Every claim that a zero refusal count means the partition is safe to apply is removed from the flag help, the operator docs, the protocol doc, and the design doc. Refs #8182. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * fix(serve): repair the child-heap assertion and the reservation leak Three findings review raised against #8508 after the partition became observation-only, all still live on this branch now that it has merged. The status assertion in `run-qwen-serve.test.ts` failed on head: it used `toEqual` against `{ mode, refusals }` while the wire also carries `maxConcurrentChildren` and `perChildCeilingMb`, so the suite was red at 217 passed / 1 failed. The local type restating the wire shape was short the same two fields. Both are filled in, and the assertion stays `toEqual` so an unannounced field still fails it — the two derived figures get matchers because this suite boots a real daemon and the pool follows the machine. What they have to satisfy is now pinned separately: a fixed ceiling times the number admitted must fit inside the pool it partitions, which is the whole reason the partition bounds anything. `decide()` and `getAcpMemoryArgs()` ran between `reserve()` and the `try` that cancels the reservation. `childHeapPolicy` is a public `createSpawnChannelFactory` option, so `decide()` is caller code and may throw; the spawn then rejected with the token held for the process lifetime, inflating `committedProcessCount` for every later spawn. Both calls move inside the `try`. The regression test is mutation-verified — reverting the move gives `expected 1 to be +0`. `ServeOptions.memoryBudgetMb` still promised a `childHeapMode: 'enforce'` that sizes children and refuses spawns. No such mode exists. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(serve): report no child-heap partition under `off` `snapshot()` returned `maxConcurrentChildren` and `perChildCeilingMb` unconditionally, so a daemon run with `--child-heap-mode off` still published a partition — 7 children at 526 MB on an 8 GB host — under a mode whose documentation says "do not model it". Review raised it, and it mattered more than it looked: with `enforce` gone, `off` and `observe` differed only in whether `refusals` incremented, so nothing on the wire distinguished a model that was switched off from one in force. Both figures are now `null` under `off`, which required widening `maxConcurrentChildren` to `number | null` in the daemon type and the SDK mirror. `null` rather than `0`: zero is already the computed answer for a pool too small to host one child at the 512 MB floor, and collapsing the two would tell an operator who disabled the model that their host cannot run anything. That leaves three distinguishable states — no policy at all (`childHeap: null`), a policy modeling nothing (`mode: 'off'` with null figures), and a live model — and each now has a test. The `off` unit test previously asserted only `refusals`, so its name ("models nothing at all when off") promised more than it checked. It now covers the figures, with a sibling test pinning 7 / 526 under `observe` on the same budget so nulling them unconditionally cannot satisfy both. Mutation-verified in both directions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(serve): never model a child heap ceiling below the documented minimum `perChildCeilingMb` is `min(floor(pool / maxConcurrentChildren), legacyChildCeilingMb)`. The first term is at least `MIN_CHILD_HEAP_MB` by construction; the second is `floor(available / 2)` and is not, so the `Math.min` could publish a ceiling *below* the `minChildHeapMb` sitting beside it in the same snapshot: avail=768 --memory-budget-mb 1024 pool=512 legacyCeil=384 perChild=384 avail=1023 --memory-budget-mb 1024 pool=767 legacyCeil=511 perChild=511 Unreachable from a derived budget — the pool reaches 0 first — but an explicit budget has a floor of 1024 while available memory does not, and `docs/users/qwen-serve.md` tells operators on exactly these hosts to pass that flag. The documented remedy is what reaches the band. Refuse the model rather than shrink under the floor, with `maxConcurrentChildren` zeroed in lockstep: a ceiling no child may run at is not a partition, and "one child fits" beside a null ceiling is the same contradiction from the other side. Nothing is applied today so the impact was a wrong published figure, but this is the number the partition asks to be judged by and the one an `enforce` mode would hand to `--max-old-space-size`. The existing matrix resolves derived budgets only, which is why the mutation sweep came back clean; add the `budgetMb` axis, asserting in each case the shape that makes it reachable, and pin the inclusive boundary (1024/1024 -> one child at 512) so nulling unconditionally cannot pass instead. Also, in the same review pass: - Split usable-gauge handling into numerator and denominator. Coercing an unusable numerator to 0 published `rssBytes: 0, rssRatio: 0, level: 'normal', source: 'rss'` — a daemon that measured nothing, indistinguishable from an idle one, which is the confusion `source: 'unknown'` and `sampled: 0` exist to prevent everywhere else here. An unusable numerator now retires its own side. Zero stays a reading for a numerator and not for a denominator. - Document that `rssRatio` divides by host total under `availableMemorySource: 'host'`, so it is a lower bound on real pressure there — a denominator problem no threshold calibration addresses. - Document that `refusals` counts channel swaps at full occupancy (the terminating child is counted until it exits) and equals the total spawn count on a host too small to model a partition. Deliberately not fixed by giving the comparison swap headroom, which would admit a 26th ceiling against a 25-child pool. - Keep the sampler's rejection handler as a documented backstop — the shipped `refreshChildResource` never rejects, but it is an optional `async` interface member, so a foreign implementation throwing early would otherwise surface as an unhandled rejection — and give it the workspace so it is attributable across the fan-out. - Test hygiene: drop a duplicated `enforced` assertion; replace a host- dependent `expect.any(Number)` with a key-set pin plus a branch, since a small host now legitimately reports no partition; use `vi.spyOn(Date, 'now')` over direct assignment; reuse the exported `ChildHeapMode` on the child-heap side, leaving the independent `memoryPressureMode` switch alone. Reported by @wenshao. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
7edc16ba11
|
feat(review): say so when the bundle is older than the review it runs (#8390)
* feat(review): say so when the bundle is older than the review it runs Every `qwen review …` step runs the BUILT bundle, not the working tree. So editing a review command, or switching to a branch that contains one, changes nothing about the run until someone rebuilds -- and the failure is silent and total: the run behaves like the last build, and every conclusion drawn from it is a conclusion about that build. Measured on 2026-08-02, dogfooding /review against #8368 from a checkout whose bundle was fourteen hours old. Three things were invalidated at once and none announced itself: `drive` and `mock-provider` had merged that morning and were absent from the binary, so "the agent never reached for them" measured nothing; and #8345's guard against scoring a mutant `survived` when its own collocated test was red had merged too, so the run reproduced the bug it fixed and filed three findings the current code holds as `inconclusive`. The round was discarded and re-run after a rebuild. `parse-args` is the first command of every review, which makes it the only place a notice reaches a reader before they act on a result. It names the file that is ahead, by how much, what actually runs from the bundle, and the command to rebuild -- "rebuild" without evidence is advice nobody can check. mtime, not git: the question is whether this bundle was built from this source, and a git comparison answers a different one. A margin absorbs a checkout, which writes everything at once in no guaranteed order. An installed package has no sources beside it, finds nothing to compare, and stays silent -- a check that cannot see the files must not accuse the build. Also documents `findings --test-delta` for users: it can lower a severity, and therefore change what the verdict is computed from, so it belongs beside `--outcomes` rather than only in the skill. * fix(review): watch the file every subcommand is registered in `packages/cli/src/commands/review.ts` is where all 30-odd subcommands are imported and registered, and it sits beside the directory rather than in it -- so a new command, or a changed dispatch, was exactly the change this check could not see. A root may now be a single file, which is what that one is. Confirmed end to end: with `review.ts` three hours ahead of a fresh bundle, the warning names it. Also two comments that did not match the code: symlinks of every kind are skipped, not only directories (`isFile()` is false for a symlinked file too), and the module now says what `QWEN_CODE_CLI` already covers -- talking to a different program -- so it is clear this guards the other half, the right program built before the change. * fix(review): compare content, because a timestamp check cried wolf The first version compared the bundle's mtime against the newest review source, and it was wrong in the direction that matters most. `git checkout` rewrites every file that differs between two commits, so returning to the branch a bundle was built from re-stamps exactly those files and the check calls a byte-for-byte correct bundle stale. Measured: with the sources untouched and the bundle two minutes older, it warned. A line that fires when nothing is wrong teaches its reader to skip the line, which would have made this worse than absent. The build now stamps a digest of the review sources it bundled into `dist/review-sources.sha256`, and the check re-derives that digest from the tree and compares. No margin to tune, no clock to trust, and no answer but the true one. Verified end to end across all five cases: a clean tree is silent, a source touched but unchanged is silent, and a real change under any of the three roots -- the command directory, the `review.ts` that registers them, the bundled skill -- warns. The digest is now one rule stated twice, since the build script cannot import the package it runs before building. `scripts/tests/review-source-digest.test.ts` holds the two equal, on this repo and on a synthetic tree that exercises the file-shaped root; a package test may not reach into `scripts/`, so it lives on the side of the boundary that may. Paths are folded relative to the repo root with separators normalised, and the file list is sorted -- `readdir` order is a property of the filesystem, so without it a bundle built in CI and a tree cloned locally would hash the same source differently and every run would warn. * fix(review): a diagnostic must not kill the run, and tests are not the bundle Two Criticals and five suggestions from review, all verified before changing anything. `writeStderrLine` throws on EPIPE, so stderr piped to `head` would have killed the review before it parsed a single argument -- a warning that destroys the run it was warning about, and the opposite of this change's own invariant. `writeStderrLineSafe` is the convention for diagnostics in this subsystem and is what it calls now. `reviewSourceRoots` builds paths with the platform `join`, and the test asserted forward-slash literals, so all three elements would have failed on the merge queue's Windows leg -- which the pull_request event never runs, so the green CI here proved nothing about it. Test files left the digest. esbuild follows imports from the CLI entry and no test is reachable that way, so folding them in fired the warning for an edit that cannot change a byte of the bundle -- the false positive this module already rejected once. 112 files became 61, and a test-only edit is now silent while a production one still warns. The handler wiring is tested at last, against a real temp tree rather than a mock of the reads under test: the derivation from `process.argv[1]`, the stamp read, and the warning. All three mutations the review named -- dropping the call, reading the stamp from the wrong directory, collapsing repoRoot to distDir -- now redden it. Also: the stamp's filename is pinned across the boundary it crosses (the build wrote a literal while the check read `DIGEST_FILE`, so a one-sided rename would have silenced the feature with every test green); the digest is computed only when there is a stamp to compare it against, instead of hashing a hundred files for a value the first guard discards; the `rebuildCommand` parameter no caller ever set is gone; and the build script's comment no longer claims a code-sharing relationship that does not exist. * fix(review): fixtures are not in the bundle either The same false positive, a third time and one directory over. Excluding tests from the digest was right and incomplete: `review/__fixtures__` holds four files — three responder modules and a captured comment — that a test loads at runtime, from no import the bundler follows. Measured against `dist`: none of the four appears in it, so editing one changed the digest while the bundle stayed byte-identical and the warning claimed a review command had changed. Both walks skip the directory now, and the parity test's synthetic tree grows a fixture and a `.spec.tsx` so the two implementations are held equal on the whole exclusion, not just the part the first case exercised. Reverting one side reddens the local case AND both parity cases, which is what that guard is for. Verified the other direction too, since an exclusion can overshoot: every review source that reaches `dist` is still covered. `DESIGN.md` and `SKILL.md` both ship and both remain in the digest — checked, not assumed, after two rounds of this exact mistake. Six cases end to end after a rebuild: a clean tree, a test edit and a fixture edit are silent; a production edit, a `review.ts` edit and a `DESIGN.md` edit each warn. * fix(review): allowlist the stamp, and stop guessing what the bundle holds The Critical first: `create-standalone-package.js` fails on any top-level dist entry outside its allowlist, and `review-sources.sha256` was on neither list. The next release would have aborted the standalone archive on all five targets, and no PR-time job runs the packager, which is why this suite is green. Allowlisted -- shipping it is harmless, since a standalone install has no `packages/` to compare against and the check stays silent there. `lib/test-utils.ts` was in the digest: test support with a production-looking name, imported by two test files and nothing else. That is the fourth patch to one rule -- `.test.ts`, then `__fixtures__/`, then this, plus `.DS_Store` -- and each was found by a reviewer after it shipped. So the rule stops being a list somebody remembers to extend: a new test asserts the property the list approximates, that every file the digest folds in is reachable from production code and nothing reachable is left out. Dropping `test-utils.ts` from the exclusion reddens it, which is the fifth instance failing in CI instead of in a review. Three branches that no test reached, each with a mutant the review measured surviving the whole suite: the walk's symlink skip (a directory cycle would send the first command of every review into unbounded recursion), the read-failure path (hashing the survivors of a concurrent checkout would accuse a tree that is merely mid-change), and the build's stamp call site (removing it left the scripts suite green while `npm run bundle` silently stopped writing the stamp). All three now redden. And `unmeasured` had no reader, so the one edge this check cannot measure but can see -- sources present, stamp absent -- passed in silence. That is the state of every existing checkout the moment this ships, and it is exactly the silent failure the change was written to end. It now says so, while an installed package, which has no sources either, still says nothing. * fix(review): the guard was shallower than the property it claimed The guard added last round asserts that every file in the digest is reachable from production code. It did not: a file imported by nothing passed, because the filter also required some test to import it; only `.ts` was inspected, so a test-only `.tsx` or `.mts` helper walked through; and it read static imports only, while this directory has nine `await import('./…')` edges. It asserts the property now — every extension, orphans included, dynamic edges seen — and the tree has no violators, so the strictness cost nothing today and is there for the next file. `__snapshots__` joins the exclusions. `vitest --update` regenerating a snapshot would have moved the digest with the bundle byte-identical; none exists under the review roots today only by chance, and 120 `toMatchSnapshot()` calls live elsewhere in this package. Three couplings that no test held: - the allowlist entry that fixed the release-breaking R2-1 -- reverting those five lines left the whole scripts suite green, and the next failure would have been a release aborting on all five targets. `isAllowedDistEntry` is exported and the stamp's own name is asserted against it, so a one-sided rename fails here instead; - the `.DS_Store` member of `NOT_BUNDLED_FILE`, absent from the repo and so from the parity tree -- one-sided removal stayed green while a macOS checkout would digest differently on the two sides forever; - each `unmeasured` reason. Swapping the two arguments at the single call site kept all 76 tests green while telling a pre-stamp checkout its sources were missing. And two comments that said the opposite of the code beneath them: the digest is computed unconditionally on purpose (the pre-stamp notice needs it), and `NOT_BUNDLED_FILE` helpers are deliberately not importers, since nothing reaches the bundle through a file the bundle does not contain. The two stderr diagnostics are documented for users, beside the sibling paragraph this PR already added. * fix(review): measure only the layout that can carry a stamp `npm start` launches `node <root>/packages/cli`, and node sets `argv[1]` to that directory -- so the derivation found sources under `<root>` with no stamp beside them and printed "could not check" on every review, forever, with advice that could never make it stop. That is the fires-when-nothing-is-wrong failure this change argues against, on the path `start.js` sets `QWEN_CODE_CLI` to precisely so reviews reach that build. Only a `<root>/dist/cli.js` layout is measured now; anything else has no stamp to find and no way to grow one. The build-side digest could kill `npm run bundle` where the check side degrades gracefully: a file vanishing mid-walk threw out of the hash loop, and the stamp is the copier's last step, so the build would fail with every asset already in place. Caught and skipped -- a missing stamp is `unmeasured`, which the runtime already treats as an acceptable answer. The skill now says what to do with the warning, which is the half that makes it reach a human: `parse-args` runs inside an agent's shell tool, the user reads the agent's summary rather than raw stderr, and a line nobody repeats is a line nobody sees -- which is how the 2026-08-02 round went wrong in the first place. It also records that the instruction cannot help the run that needs it, since the skill comes from the same bundle. And the scope is stated where silence could be over-read: the digest covers the review commands, the file that registers them, and the bundled skill -- not the shared helpers those import. A quiet run means the review code matches the bundle, not that the tree does. * fix(review): refuse to certify a bundle the copier may not describe The stamp described the tree as the COPIER saw it, and the copier runs after esbuild -- so a source edited in between, or `copy_bundle_assets.js` run on its own (it self-executes), wrote a digest certifying a `cli.js` built from something else. Silence then means "verified fresh" when it is not, and that is the only direction here where a quiet run is affirmatively wrong rather than merely uninformative: every other gap degrades to `unmeasured`. Timestamps are the wrong tool for judging staleness and the right one for judging whether this stamp can be honest at all, so the build refuses when any source is newer than the bundle it would attest to, and says why. Driven for real: touching a review source and running the copier alone now prints "skipped the source digest rather than certify a bundle it may not describe". `it('counts the same files')` compared nothing -- it asserted `> 50` on the build side while the check side exposes no count, so the title claimed a parity the body never checked, and the margin over the real 56 made it a future false alarm in `scripts/` for an unrelated change. Removed; the digest parity already holds the file set. "Root is a file" was inferred from `readdirSync` raising ENOTDIR, an assumption about every platform's libuv on the one root that is a file -- `review.ts`, where "a new subcommand was registered" lives. `statSync(root).isFile()` says it instead. And the check itself moves out of the handler into `bundleStalenessNotices`, which is where the rest of it already lived. `parse-args` is about parsing arguments again, the wording is testable without the yargs harness, and a second caller -- an agent resuming a review never runs step 1 -- is one line. * fix(review): align the twin walk, and stop a test from passing on nothing The build side still inferred "this root is a file" from `readdirSync` raising ENOTDIR, one commit after the check side stopped doing exactly that and said why. A platform that maps the case differently would drop `commands/review.ts` from one digest and not the other, and a byte-for-byte correct bundle would warn on every review forever, on that platform alone, with rebuilding reproducing the same one-sided walk. Both sides ask `statSync(...).isFile()` now. Fixing one half of a pair and not the other is the mistake this file keeps making. The filename parity test had been passing on nothing since the previous commit: it matched `writeFileSync(join(distDir, '…'))` against the script's source, the literal moved into a `stampPath` variable, and the regex returned `undefined` so the assertion compared against nothing. It runs the build against a fixture now and reads the name off `dist/`, so it measures what the build does instead of what its source looks like. Renaming the stamp on one side reddens it. Also from review: the duplicated comment block in `parse-args`; an unreadable source now says the check could not run rather than passing in the same silence as an installed package, which is what the docstring already promised; the "could not check" line no longer asserts that the checkout predates the feature, since the build has three refusal paths and one of them means the opposite; every refusal removes an existing stamp, because leaving an older attestation beside a newer bundle is a weaker form of the certifying it refuses; and `drive` calls the check, which the module comment argued for and the diff had not done -- a resumed review never runs step 1, and that is where the long work starts. * fix(review): pin the regex group the parity tree missed, and say source, not command * fix(review): allowlist what the bundle holds, and cover the drive notice (#8390) * fix(review): treat unreadable review sources as unmeasured (#8390) * test(review): pin the stamp guard mutations that survived the suite (#8390) * fix(review): close staleness-check gaps and pin the round-4 survivors (#8390) * fix(review): close round-5 staleness gaps for parity, refusals, and partial checkouts (#8390) * fix(review): close round-6 gaps in the clause classifier, symlink layout, and pin honesty (#8390) * fix(review): close round-7 gaps in the closure oracle, parity pin, and refusal pins (#8390) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): close round-8 gaps from the maintainer review (#8390) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): close round-9 nits from the maintainer review (#8390) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): pin the lease root in the synthetic digest parity case (#8390) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen Autofix <autofix@qwen-code.dev> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
6405714c9d
|
docs: document serve sub-session concurrency settings (#8404)
* docs: document serve concurrency settings * docs: clarify serve concurrency settings |
||
|
|
03eb5043cc
|
fix(dingtalk): keep status cards continuous and attributable (#8565)
* fix(dingtalk): keep status cards continuous during runs * fix(dingtalk): render attributable markdown replies * fix(dingtalk): harden status card attribution and fallback paths (#8565) * fix(dingtalk): deliver boundary content reliably and harden card fallbacks (#8565) * fix(dingtalk): halt refreshes on dead cards, keep delivered content (#8565) The per-second status chain kept pushing metadata updates after the content stream latched failed, and once the 3-failure breaker tripped an idle card could never revive. Stop the chain on a latched stream failure and keep a low-frequency probe so a recovered metadata API revives it. Also re-send boundary content declared delivered via the card when a failed/cancelled terminal overwrites the continuity card, give a re-latched status context a fresh segment id after input_requested so a later failure still reaches the card failure UX, dedup inline sender echoes, and require the 'I' of IMAGE in the partial-marker regex so a bare trailing '[' survives in final fallback text. Consolidate the duplicated content-cap constants and pin the reviewed delivery, drain, breaker, and attribution behaviors with tests. --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
e76dff1c6b
|
feat(review): add declarative repository-context manifest (#8401)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* feat(review): add OpenJDK repository context Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * refactor(review): extract repository context foundation Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): repair CI type guard and add manifest repository context Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): harden repository context per maintainer review Address both maintainer reviews on the repository-context PR: - repo-context: a PR plan whose merge base never resolved (mergeBaseSha: null) now degrades to a null artifact without consulting the worktree, instead of throwing a misleading "invalid plan" error or falling back to the PR head. - Identity reads return the same shape in PR and local modes (CRLF->LF, trimmed) and fail closed: absence yields null, a present-but-unreadable file throws. - Context-required roles can no longer override the roster's effort, topology, and mode gates. - The relatedPaths scan bound rises from 1024 visited entries to 16384 and is documented, so honestly scoped manifests no longer abort reviews. - A present-but-invalid repositoryContext now fails closed in every consumer; the gate no longer silently drops the disclosure. - The duplicated validators and bounds are shared between the wire format and the manifest provider; the context role allow-list is derived from a single const; manifest arrays no longer require hand-sorting (uniqueness only). - Nits: dead mkdir removed, output message names the provider, escape-message fix, unsafe changed paths skip instead of aborting, segment-glob regexes memoised, list helper hoisted. - Docs: user-facing manifest section, trust-boundary residuals and foundation status in the design doc, fail-closed exit guidance in the skill. * fix(review): skip unsafe related paths in manifest context (#8401) * fix(ci): align review timeout helper test with externalized variables (#8401) * fix(review): harden repository context bounds and base identity reads (#8401) * fix(review): bound manifest matching work and pin round-2 review gaps (#8401) * fix(test): isolate serve streaming suite from stray workspace settings (#8401) * fix(review): cap identity reads, bill match work by length, pin round-3 gaps (#8401) --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
02f1692d40
|
fix(web-shell): allow session refresh with daemon auth (#8445)
* fix(web-shell): allow session refresh with daemon auth * test(web-shell): cover SPA fallback shell branch for non-session navigations * test(web-shell): retarget sec-fetch SPA fallback test to non-session navigation Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(web-shell): serve pre-auth navigations during the deferred runtime window The deferred-runtime gate applied bearerAuth to every non-bootstrap request while the runtime was cold, so a browser refresh of /session/<id> (and / and /assets/*) 401'd on the default `qwen serve --token ...` start until something else warmed the runtime. Exempt the same surface mountWebShellAssets registers before auth, via a shared isPreAuthWebShellRequest predicate, so cold document navigations start the runtime and load the shell while JSON fetches, API subpaths, and --no-web daemons stay gated. The predicate is dynamically imported to respect the serve fast-path import-boundary guards. * fix(cli): align deferred pre-auth web shell gate with warm routing (#8445) * fix(cli): report daemon startup failure to pre-auth web shell navigations (#8445) A pre-auth-exempted Web Shell navigation that hit a failed deferred runtime startup fell through to the bootstrap app's bearer gate and received a misleading 401 instead of the 503 daemon_runtime_failed envelope authenticated requests get for the same failure. Track the exemption in the deferred dispatch and answer the diagnostic envelope directly. Also cover the deferred gate's HEAD exemption, which was previously untested. * fix(cli): exempt bare /assets from the deferred pre-auth gate (#8445) * refactor(cli): dedupe runtime failure envelopes and deferred-window test setup (#8445) * refactor(cli): rename startup envelope helper and pin query-string deep links (#8445) * fix(cli): serve the // root alias pre-auth and fail-close the deferred predicate (#8445) * docs(cli): record the pre-auth %2F session deep-link invariant (#8445) * fix(test): isolate serve streaming suite from stray workspace settings (#8445) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: 克竟 <dingbingzhi.dbz@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
732f4d8a29
|
feat(voice): support trusted private ASR base URLs (#8350)
* feat(voice): support trusted private ASR base URLs * fix(voice): address private endpoint review findings * test(voice): cover private endpoint edge cases * test(voice): pin remaining endpoint edge cases * fix(voice): address private endpoint review feedback * fix(voice): clarify allowlist URL and normalize IPv6 * fix(voice): harden NAT64 address validation * fix(voice): address managed endpoint review findings * refactor(voice): extract shared IPv6 transition unwrap ladder (#8350) Deduplicate the IPv6-transition unwrapping sequence (mapped, compatible, NAT64, dotted-quad) that was repeated verbatim between isPrivateNetworkIp and isAlwaysBlockedVoiceAddress on both CLI and Desktop surfaces. A single unwrapIpv6TransitionStep helper now yields the next canonical address (or 'blocked' for unrecognized ::ffff: forms), and each predicate recurses through it, preserving the exact re-check semantics at every unwrap level. * test(voice): cover allowInsecureBaseUrl wiring through desktop default transports (#8350) * fix(voice): add allowlist hint to private-network rejection error (#8350) * fix(voice): reject always-blocked base URLs before offering the allowlist hint (#8350) * fix(voice): resolve exact desktop voice provider before OAuth (#8350) * fix(voice): address review feedback for trusted private base URLs (#8350) * fix(voice): align desktop voice resolution with CLI semantics (#8350) * fix(voice): scope desktop fail-closed resolution to policy-bearing entries (#8350) * fix(voice): address round-8 review findings for trusted private base URLs (#8350) Run the invasive process-global `mock.module('ws')` suite as voice-ws-handler.isolated.ts so the desktop package's single-process `bun test` run no longer leaks the fake socket into unrelated ws consumers; the existing isolated loop runs it in its own process. Shape-guard the desktop provider scan: non-object modelProviders elements are skipped (falling through to OAuth instead of throwing a raw TypeError), and non-string baseUrl/envKey/settings.env values on a voice-model entry now surface the PROVIDER_ENTRY_REMEDY remediation error instead of crashing. Compute the DashScope-compatible /v1 rewrite before any allowlist match in fromExactModelProvider so the stage-1 check, the remediation messages, and the top-level recheck all compare the same final URL and a single allowlist entry converges for split-horizon deployments. Extend the CLI allowlist remediation messages to state which settings scopes honor the entry, since serve mode never shows the interactive workspace-strip warning. Thread providerProtocol through the CLI voice model seams (createVoiceModelSource and the daemon buildModelsConfig) so protocol-mapped custom provider groups resolve like the rest of the CLI model surface, and document the remaining protocol-agnostic desktop scan in the design doc. Correct the getHomeEnvFallback comment: it adopts the narrower getHomeEnvFallbackVars candidate set on purpose. Add multi-record DNS answer tests on both CLI and desktop net guards so the records.some classification is pinned against the array shape defaultLookupHost always produces in production. * fix(voice): address round-9 review findings for trusted private base URLs (#8350) * fix(voice): address round-10 review findings for trusted private base URLs (#8350) * fix(voice): classify desktop voice duplicates before ambiguity check (#8350) * fix(scripts): compare voice guard mirrors as parse trees (#8350) --------- Co-authored-by: rockybot2026 <265985139+rockybot2026@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
f4cf4268f5
|
fix(core): cap a streaming response's total lifetime, slim the review fan-out launch (#8602)
* fix(core): cap a streaming response's total lifetime The stream inactivity watchdog resets on every chunk, so a drip-fed stream — a gateway trickling keep-alive chunks, or a model crawling through an oversized single message — kept it alive forever while the message never completed: in issue #8597's CI review runs that meant 2.5-4.5 hours of silence ending in the outer timeout's kill. Add a per-request lifetime cap (default 15 min) that does not reset on chunk arrival. Tripping it throws a retryable ETIMEDOUT, so the existing transport-continuation recovery resumes a healthy generation the cap happened to cut. Config field streamMaxLifetimeMs; env knob QWEN_STREAM_MAX_LIFETIME_MS; 0 disables. * fix(review): carry findings lists as digest-named files, not inline agent-prompt folded the cumulative findings list into every printed verify/reverse-audit block. On a 12-14-auditor round that made the launch one 65-82 KB assistant message — the oversized single message whose stream generation never completed in #8597 — and cost a 5-10 minute paged relay through the orchestrator's context before every round. The list now goes where the brief already goes: on disk, named by the same findings digest that keys the record; the block carries a read_file pointer. The delivery guarantee is unchanged — a launch that drops the read matches no record — and the retirement scheduler's echo guard reads the list back from the file the prompt names, failing toward auditing when it is gone. * fix: address review feedback on stream guards and findings files * fix: address review feedback on findings delivery and stream guards The delivery floor counted only the brief's read receipt, so an instruction-skipping verifier could open its brief, skip the one instructed read of the findings file its block points at, and clear the gate having never seen the list it ruled on. The floor now extracts the findings pointer from the recorded prompt and requires a successful read of it — a new findings-unread delivery shape, with gap texts for the verify, reverse-audit, and combined steps. A failed findings-file write also speaks on stderr instead of silently pointing a whole round at a missing file. Stream guards: consult the lifetime deadline at the top of the loop, not only when the timer wins the race — a pre-buffered chunk resolves next() as a microtask and beats setTimeout(…, 0) every time. Merge the two ETIMEDOUT bypass blocks into one rule. New tests pin the idle-off + lifetime-on branch, the both-guards-0 disable (config and env), and the buffered-source shape. Also reword the prose sites that still described the inlined list. * fix(review): name the findings file in the verify/reverse-audit briefs The two briefs still told the agent the findings were "listed in the message that launched you", but since the list moved behind a read_file pointer they find only a pointer there. The agent reads its brief first (the block mandates it), so the stale text sent it looking for finding blocks in the launch message — the exact findings-unread shape the new delivery floor gates on. Point both briefs at the .findings.md file and the required read instead. * fix(review): address round-3 review — empty-round brief, guard hardening The b796708 brief fix stated unconditionally that the launch message points at a findings file, but an early reverse-audit round on a clean review names no file (the empty branch prints '## Nothing is confirmed yet' with no pointer) — the auditors were being sent to read a path their prompt does not contain. The reverse-audit brief now says the file is named when there is a list, and that an early round has none. Also from the round-3 review: - writeFindingsFile's failure diagnostic now uses writeStderrLineSafe: the catch exists to keep the build alive, so it must not throw out of it on EPIPE (qwen … | head). - The retirement echo-guard resolves the findings pointer from the CLI's own record (never the orchestrator's pasted copy), confined to the plan's record dir, memoized per round — an out-of-bounds or unreadable file degrades to the prompt, failing toward auditing. - Document the delivery floor's deliberate weakening: it proves the findings file was opened, not paged to completion (coverage.ts), and drop the last stale 'record folds the findings in' comment. - withStreamInactivityTimeout returns the source untouched when both guards are disabled, so the invariant survives a caller refactor (setTimeout(Infinity) would otherwise clamp to ~1ms). - settings.md + the config comment now say the stream guards are OpenAI-compatible-only, and that the 15-minute cap bounds a stream whose idle timeout was raised above it (raise or disable the cap to keep a longer window). * fix(review): address round-4 review — memo hygiene, read-visibility, naming The retirement echo-guard memoized a failed read's per-record fallback under the round's shared findings pointer, so one chunk's launch prompt would serve as a sibling chunk's findings list. Memoize only successful reads; a miss falls back to THIS record's own prompt, uncached. The findings block now prints the list's line count, so an agent whose read_file truncates can see it saw a fraction rather than the whole confirmed list — a visibility aid the delivery floor (which proves the file was opened, not paged) does not provide. Also from the round-4 review: rename withStreamInactivityTimeout to withStreamGuards (it now enforces two guards), correct the constants comment (the cap is measured from the stream's first iteration, not its first byte), and give the two stream-guard knobs a dedicated settings.md entry instead of burying them in the timeout paragraph. * fix(review): address round-5 review — deterministic record walk, accurate line count The cross-contamination regression test only discriminated under one readdir order: readRecordedPrompts walked the record dir in filesystem (filename-hash) order, so which chunk's fallback poisoned the shared memo depended on the walk. Sort the directory listing — a deterministic walk removes that whole class of order-sensitivity from a module that reasons per-record, not just from this test (a temporary revert of the memo fix confirms the test now fails against the buggy shape). findingsSection printed the line count of the TRIMMED findings body while writeFindingsFile writes the untrimmed content, so a list with leading/trailing blank lines got a label smaller than the file the agent actually reads — precisely the under-reading the count exists to make visible. Count the untrimmed content. * fix(core,review): charge the lifetime cap on upstream-wait, not delivery time Round-2's top-of-loop deadline check made the lifetime cap measure end-to-end delivery time: a healthy upstream that finished and buffered its chunks was cut for the CONSUMER's slowness (a paused IDE client, a big render), and a stream whose terminal done resolved at the boundary was converted into a retry. The cap is now charged on accumulated upstream-wait — the time the loop is blocked in await it.next() — so a buffered, already-complete stream always completes and only real upstream latency counts; the drip-fed never-completing stream spends exactly that time waiting, so #8597's shape is still caught. Also from the round-6 review: - Hoist the stream-guard error branch above the thinking-tag check: a drip-fed gateway cutting mid-<think> surfaced the guard's ETIMEDOUT as a PROTOCOL_TAG_LEAK and burned the tag-leak retry budget. - The findings line count drops the trailing newline's empty segment, so a 12-line list is not advertised as 13. - settings.md: the stream guards are env/config-only (no settings.json key); document that streamIdleTimeoutMs: 0 embedders now also need streamMaxLifetimeMs: 0 to fully opt out. - constants: a functionCall already streamed (the tool-heavy common case) recovers as a visible classified error, not a continuation. - coverage.ts: drop the stale 'four shapes' counts after Delivery grew a fifth; pipeline: dedupe the instanceof in the guard debug log; and correct the both-guards-off test's comment to pin the outcome, not the caller mechanism. * fix(core,review): monotonic guard clock, findings-write fallback, read-only floor Round-6 follow-ups the previous commit left open: - The stream guards accounted on Date.now(): an NTP step forward (or a long sleep) killed a healthy generation on the next iteration, and a backward step silently disabled the lifetime cap — the hang #8597 exists to bound. All guard accounting is now performance.now(); the setTimeout it races is monotonic too, so the two agree. Vitest fakes performance alongside the timers, and a new test A/B-verified against the wall-clock shape (it fails on Date.now() accounting). - A failed findings-file write returned the path anyway, pointing a whole 12-14-agent round at a file that does not exist — every agent burned its round, then the delivery floor failed it. writeFindingsFile now returns null on failure and findingsSection falls back to inlining the list (the pre-#8597 shape): the recorded prompt carries the list, the delivery check compares it verbatim, and the build stays alive. - The findings delivery floor matched the path in ANY successful tool call's serialized args, so a search_file_content or list_directory that merely named the file cleared it without reading a line. The transcript parser now records read_file calls apart, and the floor counts only those — a mention is not an open. * fix(core,review): round-7 leftovers — upstream-wait wording, key helper, test mock The round-6 error message and class doc still said 'total lifetime cap' and promised the continuation recovery unconditionally, and the wrap-site comment still read 'aborts at maxLifetimeMs from stream start' — all three now describe the upstream-wait semantics the guard actually implements, and the message names the wall clock separately so the two numbers reconcile. The round suffix baked into findings-role record keys and the findings file name was derived independently in three sites (findingsFileFor, runAllChunks, the single build) — a change to how a round is spelled would update two of three and silently fork the artifacts; roundPartOf spells it once. The writeFindingsFile test's module mock replaced all of stdioHelpers (three stubs, missing the other exports) and asserted mock.calls[0][0] with no mock reset — it now spreads importOriginal, clears mocks in beforeEach, and matches the stderr lines with stringContaining. * fix(review,core): round-3 follow-ups — conditional findings briefs, JSDoc placement, log labels The verify/reverse-audit briefs told the agent its findings live in the .findings.md file unconditionally, on the exact path (writeFindingsFile returning null -> findingsSection inlining the list) this PR added to work WITHOUT one; both now say 'when the message points at a findings file, read it; on the rare write-failure fallback the list is inlined in the message, read it there.' roundPartOf had been inserted between findingsFileFor's JSDoc and the function, orphaning the comment that records the one-file-per-round and null-means-inline contracts; the helper now sits above it. The guard debug log labelled the wall clock 'streamLifetimeMs' beside the cap; it is now 'wallClockMs' so the two numbers reconcile the same way the error message does. * fix(review): anchor the findings-pointer extraction to its emitted shape findingsPointerOf matched the FIRST read_file(file_path="….findings.md") anywhere in the recorded prompt. On the write-failure inline fallback the findings list occupies exactly the position the pointer would sit in, and a finding entry there can itself quote a read_file pointer of its own (a finding about this pipeline, which the harness produces when it reviews this repo). The loose match then extracted the quotation as the pointer and the readers diverged: coverage demanded a read of a path no agent was told to read (a spurious findings-unread on a run that is already degraded), and retirement, worse, confined-and-read an earlier round's file and flipped a just-filed finding to an echo, retiring a chunk that had just reported — the one direction the module's header commits to never failing. A quoted pointer inside a findings entry is indented or embedded in prose, so anchoring to a standalone read_file line removes it; the happy-path pointer is alone on its own line inside its fence. A test drives an inlined list containing a pointer-shaped line (fails under the old loose regex). --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
477859bb3f
|
feat(channels): support local gh authentication (#8461)
* feat(channels): support local gh authentication Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(channels): align registry catalog test and visuals with optional GitHub token (#8461) * fix(channels): address review feedback for GitHub local gh auth (#8461) Treat a blank replacement of an optional secret as a clear so an existing GitHub channel can no longer ship an empty or whitespace-only PAT to the daemon. Reuse the shared missing-field predicate in the editor's GitHub credential validation, wrap malformed baseUrl failures in an actionable channel error, and surface sanitized gh stderr in local authentication failures. * fix(channels): address second-round review feedback for GitHub local gh auth (#8461) Pin the whitespace-only token gate, the bounded gh stderr sanitization, and the required-secret blank-replacement guard with mutation-resistant tests. Log the authenticated account identity on channel connect so an out-of-band gh auth switch is visible to operators. Align test secret-source fixtures with the SDK union and complete the design doc's change footprint. * fix(channels): address third-round review feedback for GitHub local gh auth (#8461) * fix(channels): address fourth-round review feedback for GitHub local gh auth (#8461) * fix(channels): address fifth-round review feedback for GitHub local gh auth (#8461) * fix(channels): address sixth-round review feedback for GitHub local gh auth (#8461) * fix(channels): address seventh-round review feedback for GitHub local gh auth (#8461) * fix(channels): address eighth-round review feedback for GitHub local gh auth (#8461) --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
a123d0030a
|
ci(review): prepare evidence-image tooling for GitHub-triggered reviews (#8454)
* ci(review): prepare evidence-image tooling for GitHub-triggered reviews Reviews triggered on GitHub cannot attach images today for three reasons; this wires the two that live in the workflow: - Install tmux and freeze (pinned, checksum-verified) before the review runs. Both are optional by contract — the evidence ladder degrades honestly without them (png -> ans-only -> refused, recorded in the capture manifest) — so the step never fails the review; it only decides which rung the runner can reach. tmux mirrors the tolerant install qwen-autofix.yml already uses; freeze falls back to ~/.local/bin when passwordless sudo is absent. - Pass QWEN_REVIEW_ASSETS_REPO from a repository variable to the review step. Publishing stays OPT-IN by design: with the variable unset the env is empty and publish-assets refuses (parseAssetsRepo trims and rejects empty), so nothing changes until a maintainer sets the variable. When set, evidence images land on commit-pinned pr-assets/<pr>-review branches — already covered by the visuals cleanup workflow — pushed with the same CI_BOT_PAT the step uses. The third reason is release lag: the capture producer (capture-tui, #8388) has to merge and ship in a release before rendering claims can generate images on CI at all. This change is inert until then. * fix(ci): capture-tools step review fixes — enforced tolerance, version pin, cached fallback R1-1: the never-fails contract is now enforced twice — continue-on-error at the YAML level (the belt) and set +e with a trailing exit 0 inside (the suspenders); under the runner's default bash -e several statements (mktemp, install, sudo install with an empty path) could previously abort the step and fail the review the comment promised never to fail. R1-6: probe the VERSION, not just the binary — on a persistent self-hosted runner an installed freeze made any FREEZE_VERSION/SHA bump a silent no-op; the pin now forces a refresh when the cached binary does not match. Cached-fallback fix: put ~/.local/bin on PATH (and GITHUB_PATH) before the probe — a sudo-less runner otherwise re-downloads the tarball on every review run forever. R1-3: the step comment says capture-tui is UPCOMING (#8388, not in the released CLI) and names qwen review drive as today's tmux consumer, so the step cannot be mistaken for stale dead weight and deleted from under the follow-up. R1-4: the retention comment scopes the cleanup-workflow claim to the same-repository designation; a fork or scratch destination manages its own retention (docs updated to match, plus a note documenting the repository VARIABLE a maintainer sets to enable publishing). R1-5: the step's real bash now runs in the workflow behavioural harness under bash -e with stubbed sudo/apt/curl/sha256sum/tar/uname: worst-runner and checksum-reject scenarios exit 0 installing nothing, the no-sudo happy path pins the ~/.local/bin + GITHUB_PATH pairing, and the version-pin probe is pinned from both sides (wrong version re-downloads, matching version skips). Real freeze/sudo on a developer machine are shadowed so the tests are deterministic and can never install to /usr/local/bin. Nit: both sudo guards now check sudo -n true. * fix(ci): capture-tools step review fixes — step-owned tool dir, anchored probe, honest failures Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): capture-tools test harness — shadow tmux, don't blank its PATH dir The harness dropped every host PATH directory that ships a tmux so the step's apt branch would depend on the scenario, not on the machine hosting the suite. On GitHub-hosted ubuntu runners tmux lives in /usr/bin, so the filter blanked /usr/bin wholesale — bash, grep, mkdir, and tar included — and execFileSync('bash') died of ENOENT: all seven capture-tools tests failed in the Test (ubuntu-latest Node 22.x) job while passing on tmux-less dev machines. Replace the directory-level drop with an entry-level shadow: each tmux-bearing directory is mirrored (symlinks) into a scratch dir minus the tmux entry, in place, preserving PATH order and the empty-entry stripping the old filter did. Hosts without tmux take the map through unchanged, and Windows (no tmux in its PATH, no symlink branch) keeps its current behavior exactly. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): capture-tools test review fixes — faithful stubs, env shape pins, missing-branch scenarios Round-3 review findings: the harness executed several of the step's branches but asserted nothing about them, so probe-verified mutants (dropped tmux guard, deleted warning/degradation messages, malformed or missing FREEZE_VERSION/FREEZE_SHA256, wrong hash variable, dropped URL `v` prefix or curl `-L`, severed tarball paths, broken regex boundary, leaked mktemp dir) all shipped green. - Make the curl/sha256sum/tar stubs model their real contracts: exact pinned URL, pinned checksum over a file curl actually wrote, existing -xzf operand - Pin FREEZE_VERSION/FREEZE_SHA256 shape in captureToolsSource - Pin the full curl flag set and the three-site tarball path agreement - Assert the stale-renderer warning (fires on degraded re-download, silent on the happy path) and the tmux-unavailable message - Pin TMPDIR and assert the mktemp cleanup leaves it empty - Add the two missing scenarios: tmux-present skips apt, cached version extending the pin with a leading digit re-downloads Verified by 13 mutation probes: every named mutant now turns the suite red (13/13 killed), baseline 34/34 green. * fix(ci): capture-tools step review fixes — hash-verified cache, per-run PATH promotion * fix(ci): capture-tools step review fixes — verified-bytes-only installs, step timeout Review findings on the capture-tools step: - Drop the PATH-trust branch: a freeze already on PATH was accepted on its own --version and executed to probe it — exactly the self-report the FREEZE_BIN_SHA256 comment declares attacker-controllable, from dirs writable between jobs on both runner classes. The checksummed download always runs now; the cache makes it free after the first run. - Guard $tools_bin in the download branch: with mktemp failing, the unguarded install resolved to /freeze — harmless unprivileged, but a root-in-container self-hosted runner writes it and reports success with nothing on PATH. - Copy-then-verify the cache: install into the fresh per-run dir FIRST, verify THOSE bytes, delete both copies on mismatch — the verified bytes are the bytes later steps execute, closing the check-then-copy race for free. This makes the separate pre-verify block redundant; it is deleted. - Add timeout-minutes: 5 — continue-on-error bounds failure, not duration, and a stalled `sudo apt-get update` mirror had no other bound under the 300-minute job cap. - Report block: say the resolved freeze is likely broken when its --version produces nothing, instead of echoing a blank line and calling it stale; the mismatch wording is direction-neutral now. Tests: replace the PATH-trust scenario with a planted-PATH one (marker outside the scenario dir proves the plant never executes), add the mktemp-failure scenario (the install stub succeeds like root would, so the unguarded mutant is caught) and the promoted-dir 0700 assertion; re-anchor the two digit-boundary tests on the report's warning. 41/41 green; both fix mutants verified killed. * fix(ci): capture-tools review fixes — stale-dir cleanup, pinned guards Address round-5 review: - R5-1 (Critical): the per-run qwen-review-tools.* dir under RUNNER_TEMP was never removed; RUNNER_TEMP survives across jobs on the shared pool, so every review run accumulated one dir + one Go binary, unbounded. 'Clean stale agent state' now removes stale dirs before the install step creates the current run's dir, matching the qwen-triage.yml convention. The harness comment claiming the dirs were runner-cleaned is corrected. - R5-6: the cache re-verification rejection branch now logs why it deletes the cached binary instead of degrading silently. - R5-7: bump-checklist note beside the freeze pins — the harness stubs key on the same env values, so a transposed hash pair must be caught against the real release artifacts at bump time. - R5-2/R5-3/R5-4/R5-5: four unpinned step properties now pinned (the if: guard, the sudo -n probe flag, install-after-context ordering, and the cache branch's tools_bin guard via a new mktemp-fails scenario); six mutation probes confirm each pin kills its mutant. * fix(ci): capture-tools review fixes — curl budget, swept scratch dir, wiring pins * fix(ci): capture-tools review fixes — harness mutation pins, pin-pair self-check Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): capture-tools review fixes — shadow-farm cleanup, backoff budget term * fix(ci): capture-tools review fixes — report probes only installed freeze, age-gated sweep --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
1a2bb10298
|
fix(review): admit evidence images by content, not by name — magic-byte sniffing (#8459)
* fix(review): admit evidence images by content, not by name — magic-byte sniffing The publish-assets allowlist was extension-based, and an extension is a claim anyone can make: combined with a prompt-injected review run, whatever could name a file evidence.png could host up to the size cap of arbitrary bytes at a github.com URL through the evidence push (raised in #8454's review as an enablement consideration). sniffImageFormat reads the four admitted signatures (PNG, JPEG, GIF87a/89a, RIFF+WEBP — RIFF alone is not enough, AVI and WAV share the container prefix) and validateAssetContent rules the content against the format the extension claims, fail-closed: an unrecognized signature refuses even when the extension is allowed. publish-assets applies the ruling to every file's first bytes before anything is uploaded — all-or-nothing, same refusal contract as the other gates. Pinned: the four signatures, truncated/empty headers, the RIFF/AVI near-miss, extension-format mismatch, fail-closed unknown extensions, and end-to-end: a shell script named evidence.png refuses with exit 3 and nothing pushed. * fix(ci): re-pin review timeout tests to vars externalization (#8459) * fix(review): single-source the asset allowlist and pin sniff depth (#8459) Address round-1 review suggestions: - ASSET_EXTENSIONS now derives from EXTENSION_FORMAT, so admitting a format is a one-place change the batch gate and the content gate cannot drift on; the duplicated extension extraction moves into one claimedExtension helper shared by both gates. - Pin what the mutation probes showed unpinned: the GIF87a branch (sniff + admission), uppercase extensions at the content gate, the refusal message direction, the full depth of every signature (near-miss negatives), and the publish-time 16-byte slice end to end (a WEBP publishes through runPublishAssets; verified the test fails when the slice shrinks to 8). * fix(review): pin every sniff check and guard the allowlist lookup (#8459) * fix(review): pin every sniff byte and the two-gate format coupling (#8459) * fix(review): align the two-gates comment with the pin that enforces it (#8459) * fix(review): sharpen evidence-gate diagnostics and pins (#8459) - Content refusals now name the JSON-quoted full path, so two same-named files from different directories are tellable apart (the sibling read-error refusal already spent the path). - Export ASSET_HEADER_BYTES from the lib that owns the sniff depth; the publish call site uses it and the two-gates pin slices canonical headers to it, so a future longer signature fails the pin, not real publishes. - One shared refusal builder keeps the allowlist message identical in validateAssetFile and validateAssetContent. - Narrow the sniffImageFormat threat model to what magic bytes buy: binds the claimed type to the leading bytes, does not stop prefixed payloads. - One-byte-off matrix becomes a labeled it.each table (failures name the exact corrupted byte); imports re-alphabetized. * test(review): pin the shared extension refusal across both asset gates (#8459) * test(review): pin lastIndexOf extension parsing for multi-dot asset names (#8459) * fix(review): admit WEBP by its fourcc and name refused files once (#8459) --------- Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
32e2741577
|
perf(core): clear tool results to a low watermark to preserve prompt cache (#8464)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* perf(core): clear tool results to a low watermark to preserve prompt cache Size-triggered microcompaction now clears oldest compactable tool results down to half the threshold instead of stopping just below it, so the conversation prefix stays stable between clearings and provider prompt caches keep matching. The recent-result budget now protects committed results only; pending results no longer consume protection slots but stay counted, uncleared, and live for file-read-cache resolution. Adds the watermark to cleanup metadata and the debug log. Fixes #8463 * fix(core): harden size-cleanup protection against zero-char and pending refs Review follow-up for the low-watermark change: keepRecent now selects from committed results that are actually clearable (positive, successful, uncleared output), so trailing errors, prior placeholders, and empty outputs no longer absorb protection slots. Pending refs are dropped from the keep set entirely — a pending read may be a cache-hit placeholder rather than file bytes, so it must not suppress eviction reporting; over-disarming only costs a redundant re-read. Adds regression tests for both plus the protected-saturation consecutive-trigger corner. * qwen: address PR review feedback (#8464) Pin the (soft-exceeded) log marker with the one-line assertion suggested by the sandboxed verification report (finding S-1): the all-protected overage test now asserts 'target 250000 (soft-exceeded)', killing the surviving mutant M4. * qwen: address PR review feedback (#8464) * qwen: address PR review feedback (#8464) * qwen: address PR review feedback (#8464) Two P1 context-integrity fixes from review: (1) media-only tool results (image/PDF reads with empty text output and bytes on functionResponse.parts) stay in the idle-path keepRecent candidates instead of being dropped by the zero-char filter; (2) only write_file results vouch for file residency in kept-path accounting — edit calls carry just old/new snippets while still setting the cache's sticky full-read flags, so a kept edit can no longer suppress eviction reporting after the full read is blanked. Regression tests for both. * qwen: address PR review feedback (#8464) Pin the absence of the (soft-exceeded) marker at the exact watermark boundary: clearing that lands the virtual total exactly on the watermark must not be flagged. Kills the >= and always-true mutants of the marker condition that previously survived the suite. * qwen: address PR review feedback (#8464) --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
5631f4b112
|
feat(serve): add a required external tool guard provider (#8125)
* feat(serve): add required external tool guard * fix(serve): keep guard constants off fast path closure * test(serve): cover guard startup options * refactor(acp-bridge): centralize external tool guard validation and ack value (#8125) * fix(core): align MCP reconnect timeout test with safe replay policy (#8125) The reconnect-on-timeout test still built its mock tools without server trust or tool annotations, which the safe replay change now requires before automatically replaying a connection-loss failure. Update the fixtures the same way the surrounding reconnect tests were updated, keeping the test's original assertion that a timeout on a known disconnected server goes through the reconnect path. Mirrors the same alignment already landed on main. * fix(cli): alias externalToolGuard subpath for vitest source resolution (#8125) This PR added `@qwen-code/acp-bridge/externalToolGuard` imports to cli serve/acp modules but not the vitest source alias every other acp-bridge subpath carries. Without it, any vitest run whose acp-bridge dist is stale or absent fails to resolve the import and the five serve test files die at transform time. Add the alias following the documented convention in the config so tests read the live source. * fix(serve): reject non-ASCII external tool guard bearer tokens A token outside the ASCII range passed construction but made the handshake throw ERR_INVALID_CHAR when interpolated into the Authorization header, blocking qwen serve startup in required mode with an unexplained error. Enforce printable ASCII (0x21-0x7E) at validation time so the configuration fails fast with a clear message. --------- Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
6e9ecc41e4
|
fix(core): resolve DashScope thinking-knob conflicts by family (#8488 round 2) (#8536)
Second review round on #8488: - Honour an explicit extra_body enable_thinking: false on the qwen3.8-max family as reasoning_effort: 'none' instead of silently deleting it and re-enabling thinking - Legacy qwen hybrids now drop the inert reasoning_effort override when it conflicts with a meaningful thinking_budget, keeping the knobs the model actually reads - Family-gate the pipeline's enable_thinking tool_choice clause like the reasoning_effort clause: on non-qwen models sharing the endpoint the field is an opaque no-op (GLM reads thinking.enabled), and stripping forced tool selection there degraded their side queries - The tier-native disable path emits reasoning_effort: 'none' — the knob the family reads — instead of enable_thinking: false; the required-thinking retry trigger recognises the new shape so runtime learning still fires - Warn once per generator (not debug per request) when user extra_body knobs are dropped; hoist the wire-model family predicates to modalityDefaults.ts and share the provider's extra_body merge tail - Tests for every behavior above (all load-bearing, verified by targeted mutation); docs attribute the vendor rejection to thinking_budget only and document the extra_body exceptions |
||
|
|
ac67de2e46
|
fix(review): stop the reverse-audit loop while there is still time to report (#8468)
* fix(review): stop the reverse-audit loop while there is still time to report Measured on CI run #8368 (+1699 lines): the iterative reverse audit ran to its 5-round cap, each round a per-chunk fan-out whose findings then went back through verification, and the loop consumed 3.5 of the job's 4 budgeted hours. The outer GNU-timeout kill arrived while round 5's findings were still being verified. The review died holding every confirmed finding it had; nothing reached the pull request. The loop's rounds are driven by the orchestrator, but every round begins at the same place: agent-prompt building the round's prompts. So the builder becomes the loop's clock. When the environment carries a review deadline (QWEN_REVIEW_DEADLINE_EPOCH, exported per attempt by the review workflow) and the remaining time is inside the reserve kept for the last verification, compose-review and submission (default 60 minutes, QWEN_REVIEW_DEADLINE_RESERVE_SECONDS to override), a reverse-audit round is refused: a BUDGET line on stderr, exit code 4, no prompt built and no record written. The message carries the exact unreviewedDimensions entry to file, so the disclosure that caps the verdict is the CLI's text, and Step 6 proceeds with the findings already confirmed. Local runs have no deadline and are untouched. A malformed deadline fails open — the outer kill still bounds the run, and a broken variable must degrade to today's behaviour rather than wedge every budgeted review at round 1. The verifier is deliberately not gated: the reserve exists so it can run. * fixup: scale the deadline reserve to the externally-chosen budget The budget is not this workflow's to assume: it arrives from a repository variable, a workflow input, or a /review --timeout=N comment. A fixed 60-minute reserve would consume most of a 70-minute budget and refuse the audit loop outright on a 30-minute one. The workflow now passes a reserve of a quarter of the attempt, floored at 10 minutes and capped at 60; the CLI constant remains only the fallback for a caller that sets a deadline without a reserve. * review feedback: admit the round only if IT fits, and cap deterministically Three findings from review, all taken: 1. The gate budgeted for the tail but not for the round it admits — the terminal round is by construction the one that starts closest to the boundary, so the killed-mid-verification failure survived one round wide. The gate now requires remaining >= round + reserve, where the round's cost is the previous round's, measured admission-to-admission from a stamp the builder writes (one per round; a same-round rebuild is not a round), falling back to a 30-minute constant for round 1, which starts with the most headroom. 2. The refusal was deterministic; the disclosure that caps the verdict was prose the orchestrator had to carry. The builder now records a budget-stop marker beside the prompt records and compose-review synthesizes the unreviewedDimensions entry from it — deduped against a relayed copy — so a run that drops the sentence still cannot approve past a truncated audit. 3. Exit code 4 is documented in the command's describe. Also restores the Step 5 bullet the previous commit's edit displaced (new findings merge into the cumulative list before the next round). * review feedback: pin the budget gate's all-chunks refusal and ordering Cover the two behaviours the review noted were only asserted on the bare --findings form: an exhausted budget refuses the loop's real --all-chunks round before ANY of the per-chunk records is written, and a malformed call (--round 0) still gets its validation error first — exit 4 is for a well-formed round the budget refuses, never a replacement error. Also name what the code already does: reserve=0 is the deliberate escape hatch (the gate shrinks to the round estimate alone), and the workflow's 3600s cap mirrors DEFAULT_RESERVE_SECONDS. * docs(review): describe the soft-deadline env vars for time-budgeted runs The review noted the two new variables appeared in no user-facing doc; the reserve in particular is an operator-facing knob. State what each does, the fail-open posture, and how the refusal surfaces in the verdict. * fix(cli): align budget-stop disclosure with the gate's refusal (#8468) A round-1 budget refusal left no reverse-audit records, so the Step 4/5 floor reported the deliberate stop as a rogue/unlaunched audit with a rebuild FIX the same gate deterministically rejects; the refusal's own disclosure was swallowed by the caller-echo dedup. The floor now stands down when the budget-stop marker exists, and compose-review renders the disclosure structurally, bilingually, from the marker. Also: `--role reverse-audit` requires `--round <k>` (an unlabeled admission stamps an entry no estimate can attribute), the budget gate runs after the plan/findings reads (a broken plan or unreadable findings deserves its own error, and nothing is stamped ahead of a buildable call), and the gate's admission boundary, measured-cost behaviour, and the workflow env contract are pinned by tests. * review: a budget stop excuses only the round it refused The budget-stop suppression keyed on the marker's existence alone, so every reverse-audit gap shape went silent once any round was refused — including the shapes that describe rounds which RAN before the budget hit. A hand-written round-1 launch is exactly as undelivered when round 3 later hits the budget, and suppressing its disclosure let 'stopped before round 3' imply the rounds that did run were faithful. Exactly one shape is by design under a marker: not-built — the refusal writes no record, so an audit with no records IS the audit the gate stopped, and its FIX (rebuild the round) would be refused by the same gate. The suppression now names that shape and no other; a rewritten, unlaunched or brief-unread round keeps its disclosure and its repair. The new test pins the operative halves: the verdict stays capped, the marker's disclosure posts, and the operator channel carries the rewritten round's exact repair. (The posted body collapses same-subject disclosures — both say 'reverse audit' — so the author sees the stop; repairs are acted on from stderr, where the rewritten fix rides.) * fix(review): fence budget state per run, and let gate errors beat budget stops Address the round-2 review threads on the reverse-audit budget gate: - Fence budget-rounds.json and budget-stop.json by the plan's own mtime. Every run rewrites the plan at its Step 1 capture, so records older than the plan belong to a previous run of the same PR: a run killed before cleanup no longer prices the next run's rounds off stale stamps (an hours-old stamp read as an hours-long round refused round 1 of a fresh budget) and no longer caps a later run's verdict on a stop that did not happen in it (R2-1, R2-2). - Refuse a structurally unbuildable plan (no chunks[], duplicate or non-integer ids) with its own error ahead of the budget gate, so the same corruption gets the same diagnosis whatever the clock says, and no budget-stop marker is written over a corrupt plan (R2-5). - Stamp a round admitted only after its build succeeds: a build that throws leaves no stamp, so the next round's cost is never measured from a build that produced nothing and floored to 600s (R2-6). - Keep the budget entry's 'reverse audit' subject out of the caller-echo prefix filter: other reverse-audit scopes the orchestrator disclosed (a twice-whiffed chunk from the rounds that DID run) are no longer silently dropped in the marker's shadow; the marker's own relays stay deduped by the phrase splice (R2-7). - Render --round unbracketed in the reverse-audit rebuild fix — the CLI refuses a round-less reverse-audit call, so the paste-and-run repair must not present the flag as optional (R2-14). - Document the deliberate one-verification overlap between the measured round estimate and the tail reserve, at both definitions (R2-13). - Test hardening, each assertion mutation-probed to fail its named mutant: a reshaped relay only the marker-phrase splice dedups (R2-8); the stamp's round label and the verifier's no-stamp invariant (R2-9); whole-line, unit-arithmetic and reserve-cap pins on the CI wiring contract (R2-10); the first-wins stamp survivor (R2-11); the reserve=0 escape hatch (R2-12). --------- Co-authored-by: verify <verify@local> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
874e46d734
|
fix(core): harden Qwen 3.8 reasoning effort wire shape (#8488)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
* fix(core): harden Qwen 3.8 reasoning effort wire shape (#8472 follow-up) Follow-up to #8472, addressing the post-merge review findings: - Drop enable_thinking/thinking_budget after the extra_body merge whenever reasoning_effort ships: the Token Plan preset made qwen3.8-max-preview carry both thinking knobs, and DashScope rejects reasoning_effort combined with thinking_budget - Family-gate the new tool_choice=required strip clause to qwen wire models: reasoning_effort is an opaque sampling override on non-qwen DashScope models, and dropping forced tool selection degraded their structured side queries - Prefix-match the qwen3.8-max family so dated snapshots and -latest aliases receive the selected tier instead of silently collapsing to enable_thinking - Log the tool_choice strip; restore the effort-config JSDoc and comment the request-level override copy * fix(core): family-gate DashScope thinking-knob drop (#8488) --------- Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com> |
||
|
|
48d37cdf70
|
docs: document headless Goal workflows (#8503) | ||
|
|
1c1ee23f93
|
feat(core): support Qwen 3.8 reasoning effort (#8472)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
|
||
|
|
7dfc554dff
|
feat(review): Add structured Web Shell review results (#8402)
* feat(review): add Web Shell review artifacts Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(web-shell): add code review artifact visual scenario (#8402) * fix(review): address Web Shell review artifact feedback (#8402) * save-artifact: document why paths resolve against the daemon workspace root (QWEN_CODE_PROJECT_DIR) instead of cwd, and cover the relative-path form the skill documents with a test where the two roots differ. * CLI/renderer contract: the renderer hand-duplicates the findings vocabulary and fails closed on unknown values, so name the renderer as a second consumer beside the CLI's lists and check in a contract fixture generated through the real pipeline (validateFindings -> buildReport -> save-artifact) that exercises every source, severity, confidence and outcome. Exporting the vocabulary through the SDK stays deferred: it is a public cross-package API change beyond this PR's seam. * resolve-anchors now validates `line` exactly like `findings` does (positive safe integer); the two validators in one pipeline no longer disagree. Note: an in-flight `.qwen/tmp` findings file carrying `line: 0` fails where it previously did not. * The renderer validates markdownReportPath (relative, no ".." segments, .md suffix) before it becomes a readWorkspaceFile call, resets the severity/confidence filters when switching artifacts, and surfaces heldByMeasurement so a nonzero Held count is attributable. * save-artifact refuses low effort structurally (choices and library guard) instead of by prose, stats the Markdown report before reading it so a directory reports "not a file", and the component no longer shadows the DOM `document` global. * The case-insensitive alias test now skips visibly on case-sensitive filesystems instead of passing vacuously. * Comment the kept `turnOutputs.review` key and document the JSON companion in the user docs. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): address second Web Shell review artifact feedback round (#8402) --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
0cb109f513
|
fix(core): Avoid replaying unsafe MCP tool calls (#8387)
* fix(core): Avoid replaying unsafe MCP tool calls Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): Revalidate MCP replay after reconnect Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
2ae8cd9619
|
feat(memory): configure background agent turn limits (#8171)
* feat(memory): configure background agent turn limits Signed-off-by: ahmadalguydi <ahmadalgaidy@hotmail.com> * fix(memory): honor configured skill review turn limits Signed-off-by: ahmadalguydi <ahmadalgaidy@hotmail.com> * test(memory): cover planner turn limit sentinels * fix(memory): apply turn limits to all agents * test(memory): cover remaining turn limit feedback --------- Signed-off-by: ahmadalguydi <ahmadalgaidy@hotmail.com> |
||
|
|
186812694c
|
feat(review): publish evidence images to a user-designated assets repo (#8351)
* feat(review): publish-assets — evidence images for PR review comments
GitHub's API cannot attach images to review comments (the web UI's
drag-and-drop upload has no API equivalent), so a review whose evidence is a
screenshot — a TUI rendering, a before/after comparison — had no way to show
it. New `qwen review publish-assets` hosts evidence images in a
user-designated repository and hands back URLs a comment can embed.
Grew from the maintainer's manual workflow (screenshots pushed to
`pr-assets/<PR>-verify` branches over HTTPS), and inherits the shape of the
skill's only other public write (`submit`) deliberately:
- Designated destination: writes only to QWEN_REVIEW_ASSETS_REPO, an
owner/repo the user set by hand — the reviewed repo for maintainers, a fork
or scratch repo otherwise (fork-vs-in-repo becomes a configuration
difference, not two code paths). A separate variable from
QWEN_REVIEW_SCRATCH_REPO on purpose: that contract forbids PR-derived
content, and evidence screenshots are exactly that. Unset → exit 3.
- Authorised run: the same args-file re-parse and target binding as submit,
now extracted to a shared lib/authorization.ts so the two gates cannot
drift (the target-binding lesson lives in one place). Since an effective
--comment forces high effort, low/medium runs can never publish.
- Images only, capped, all-or-nothing: extension allowlist (SVG excluded — a
script container), per-file and per-batch size caps, one refused file
refuses the batch before anything is pushed.
- Immutable references: files land on pr-assets/<pr>-review via the Contents
API (HTTPS via gh; no clone, no SSH), content-hash-named so re-runs are
idempotent, and every URL is pinned to the commit — a posted comment's
evidence cannot be changed from under it. The web-host /raw/ URL form works
unchanged on GitHub Enterprise.
- Auditable: a manifest names every file pushed and the landing commit,
swept by cleanup with the other review artifacts.
The findings artifact gains per-finding `assetFiles` (local evidence paths)
and `assets` (published URLs); `publish-assets --findings/--findings-out`
publishes everything referenced and weaves the URLs back in, so the comment
builder reads the artifact rather than hand-carrying URLs.
What the command cannot check is stated in SKILL.md instead: image content.
Publish only evidence the review itself produced — never a capture of the
user's own terminal, which can hold an env dump in the scrollback.
Tests: 45 files / 1394 assertions — new suites for the assets naming and
validation rules and the command's gates (refusal without designation,
refusal without authorisation, target binding, branch creation, idempotent
re-run, batch refusal, findings weaving); submit's 42 pass unchanged on the
extracted gate.
* fix(review): publish-assets round-1 self-review — six findings
Round-1 review of this branch, walked with the angles the author-side pass
does not cover:
- submit.ts kept its parseReviewArgs import after the authorization
extraction; vitest does not typecheck, `tsc --build` does, and CI's build
leg failed on TS6133. (The whole first CI round's failures cascade from
this one break.)
- ensureBranch %2F-encoded the slashed ref path; GitHub's documented form is
literal slashes and %2F routes inconsistently across endpoints — a 404
here reads as "branch missing" and turns every re-run into a 422 on the
create. Ref paths are now literal (the branch name is built from a
validated integer, so interpolation is safe); the contents `?ref=` query
VALUE keeps its encoding, which is the correct position for it.
- The authorization gate bound URL-shaped `--comment` arguments against the
ASSETS repo, refusing legitimately authorised runs whenever the assets repo
is a fork rather than the reviewed repo. The shared gate's repo binding is
now optional — submit still always binds it; publish-assets binds the PR
number (and host) alone, with a new optional --reviewed-repo to restore
the stronger binding when the orchestrator knows the reviewed repo.
- URLs were pinned to the last PUT response's commit.sha; on an
identical-content update that field's shape is GitHub's to decide, not
ours to assume. The head is now read from the branch ref after the
uploads — one extra call for independence from the response shape.
- putContent's catch-all retried EVERY failure through the exists path,
answering a 401 with a confusing secondary error from the sha lookup; the
retry now fires only on the 422/needs-sha shape and rethrows the rest.
- --findings without --findings-out silently skipped the URL weaving; it
now warns, and --findings-out implies --findings.
New tests: literal-ref assertion, non-exists rethrow, URL-shaped
authorisation without assets-repo binding, --reviewed-repo mismatch refusal.
45 files / 1399 assertions green; `tsc --build` clean.
* fix(review): publish-assets round-2 — empty-findings no-op, reviewed-repo hint
Round-2 findings on this branch:
- A findings artifact carrying no assetFiles is the ORDINARY case for most
reviews, but publish-assets answered it with exit 3 — a refusal an
orchestrator calling the command unconditionally on every posting run
would read as a failure to repair. It is now a no-op (exit 0,
{published:false, count:0}); a bare --files with nothing named keeps the
exit-3 refusal, because there the emptiness IS the caller error.
- SKILL.md's example now names --reviewed-repo for URL-target reviews, so
the stronger authorisation binding is used where the orchestrator knows
the reviewed repo.
44 files / 1387 assertions green; tsc --build clean.
* test(review): fix invalid two-argument expect in assets.test.ts
Round-3 sweep: vitest's expect takes one argument — the message-style second
argument was a lint error and a weak assertion both. The offending value now
rides inside the asserted object, so a regression names which shape slipped
through instead of reporting 'expected true'.
* test(review): pin the findings schema's evidence-asset validation directly
Round-4 sweep: assetFiles/assets were exercised only through publish-assets'
weaving test — the schema's own rejection paths (non-array, empty-string
entry, empty-array drop) had no direct case, so a validation regression
would have surfaced as a confusing weaving failure two layers up.
* fix(review): address all six findings from the automatic review (R1-1..R1-6)
The /review pipeline's own round-1 findings on this PR, each confirmed and
fixed:
- R1-1 (the real catch): the host-binding check sat nested inside the
`req.repo !== undefined` guard, so a caller omitting --reviewed-repo also
silently skipped the HOST binding — contradicting the documented "binds
the PR number (and host) alone". The host check now stands on its own;
a new test pins an Enterprise-host mismatch refusal with the repo binding
absent.
- R1-2: --pr accepted whatever yargs `type:'number'` passed through (NaN,
0, 3.5), and --user-authorized bypasses the gate that would have
re-parsed the target — `pr-assets/NaN-review` was reachable. A Gate-0
positive-integer check now refuses first, matching submit's sibling
discipline.
- R1-3: the suite drove the skillArgs seam without clearing
QWEN_CODE_SESSION_ID, so running it inside an active Qwen Code session
spuriously failed eight tests. beforeEach now saves/clears the variable
and afterEach restores it.
- R1-4: the 40MB aggregate cap was enforced inline and untested (a mutation
deleting it stayed green). The per-file rules and the total cap now live
in one pure ruling, validateAssetBatch, unit-tested with five 9MB sizes
and no fixtures.
- R1-5: the asset_files snake_case alias was the one untested member of an
otherwise-tested alias family; pinned.
- R1-6: the setGhHost wiring had no command-level assertion; a GHE test now
pins both the call and the host-carrying manifest URLs.
44 files / 1397 assertions green; tsc --build and eslint clean.
* fix(review): address all ten round-2 findings from the automatic review
Round-2 of the /review pipeline on this PR: 2 Critical, 8 Suggestions,
every one confirmed against the code and fixed.
Criticals:
- The round-2 test block added for the empty-findings no-op omitted the
QWEN_CODE_SESSION_ID save/delete/restore its two sibling blocks perform,
so the suite spuriously failed inside an active Qwen Code session — the
exact dogfooding environment this repo reviews from.
- The gh routing and the returned URLs read the host from two different
sources: with --host absent, gh children inherit an operator-exported
GH_HOST (routing at Enterprise) while rawAssetUrl defaulted to
github.com — every returned URL a 404. One effectiveHost (flag, then
GH_HOST env) now feeds both.
Suggestions:
- putContent's retry discriminator matched a bare `422` anywhere in
err.message — which execFileSync fills with the full command line,
including the PR-numbered remote path: evidence for PR #4220 would read
a 401 as "already exists". Anchored to `HTTP 422`.
- ensureBranch's bare catch read every ref-lookup failure (401, 403
rate-limit) as "branch missing"; only HTTP 404 takes the create path
now, and an empty assets repo — whose default_branch resolves while its
head ref 404s — is named as the condition it is, with the fix stated.
- Validation refusals threw (yargs exit 1, stack trace, empty stdout)
while every other gate in the command answers exit 3 +
{"published": false}; unreadable files and batch refusals now speak the
same refusal language.
- The command's idempotent writes (content-hashed PUTs, a ref create
whose duplicate is tolerated) now go through a new ghWithInputRetried —
sharing gh()'s transient-error retry — and ghWithInput's no-retry
docstring names the two-caller split instead of claiming a sole caller.
- parseAssetsRepo admitted dot-segment repos (`owner/..`) its docstring
claimed were path-safe; segments now exclude `.`/`..`, mirroring
submit's isRepo.
- stringArray accepted whitespace-only evidence paths; trim(), matching
the sibling asString.
- The GHE test asserted setGhHost was called but not WHEN; it now asserts
the call precedes the first API invocation.
44 files / 1403 assertions green; tsc --build and eslint clean.
* refactor(review): one refusal helper for every publish-assets gate
Round-2 of this branch's fresh review: the refuse() helper existed below
seven inline copies of the identical three-line refusal — the drift shape
where one site eventually forgets the exit code. Hoisted to the top of
runPublishAssets and used by every gate; message content unchanged where
tests pin it. 26/26; tsc clean.
* fix(review): address the round-3 review — bidirectional host binding and 14 more
The automatic review's third round on this PR: 1 Critical + 14
Suggestions, each verified and addressed.
The Critical (host binding, both halves):
- The gate's `req.host &&` guard bound the host in one direction only —
an Enterprise-URL authorisation admitted a host-less write routed at
github.com (or wherever GH_HOST pointed). The gate now compares the
authorised host against the write's EFFECTIVE host, defaulting an
absent req.host to github.com: a host is a host, not an exemption.
- Both callers fed the gate the flag rather than the route: publish-assets
computed effectiveHost (--host ?? GH_HOST) AFTER the gate and bound
args.host; submit bound args.host while its gh child inherited GH_HOST.
publish-assets now resolves effectiveHost before Gate 2 and binds it;
submit binds the same resolution.
The rest:
- pr-assets/<N>-review registered in the asset-branch cleanup workflow,
per its own every-producer-must-be-added-here rule — a branch nothing
deletes is permanent.
- ghWithInputRetried had been inserted between ghWithInput and its JSDoc,
leaving the does-NOT-retry comment attached to the function that DOES
retry; each function now carries its own doc.
- putContent's retry-path contents-GET is wrapped: when the 422 was not
the sha-missing shape and the path does not exist, the GET's 404 no
longer replaces the PUT error the user needs.
- stringArray treats null as absent like every sibling parser, so an
artifact rendering "no assets" as null canonicalizes instead of
crashing.
- Test isolation, all four describe blocks: GH_HOST save/delete/restore,
setGhHostMock.mockReset (a sibling's persistent throwing implementation
survives mockClear — the malformed-host test also switched to
mockImplementationOnce), and full mock resets in the blocks that lacked
them.
- The two regression-pin tests the review measured vacuous now
discriminate: each fails only the one call under test and asserts the
pipeline stopped THERE (no contents PUT after a bad create; exactly one
gh call after a 403 lookup).
- New positive pins: a double-fired create ("Reference already exists")
succeeds; the canonical report shape this command's own --findings-out
writes round-trips; an Enterprise-URL authorisation refuses a host-less
write while a github.com-URL one passes it.
Not changed: the finding that reverting the Finding-interface hunk leaves
tests green — the fields are type-level and their removal fails
tsc --build (the CI leg that caught this branch's own TS6133); a runtime
pin would duplicate what the round-trip tests already exercise.
47 files / 1495 assertions green; tsc --build 0 errors; actionlint clean
on the cleanup workflow.
* fix(review): address the round-4 review — empty-GH_HOST passthrough and four test pins
Round 4 came back COMMENTED (down from CHANGES_REQUESTED), 5 Suggestions,
0 Criticals — all five confirmed and fixed:
- An exported-but-empty GH_HOST ("" from an unset workflow var) survives
`??`, being non-nullish: effectiveHost became "" and the gate compared
the authorised host against "", refusing a legitimate github.com write.
Both call sites now collapse an empty trim to undefined (`|| undefined`,
parenthesized).
- The gate's URL-shaped repo/host binding was exercised only via
publish-assets' suite; submit.test.ts now pins both directions of the
host binding and the repo binding at its own call site.
- ghWithInputRetried had no retry-contract test; gh.test.ts adds the
symmetric block to ghWithInput's does-NOT-retry pin (transient 500
retried once then succeeds; non-transient 401 single call).
- The publish-assets mock aliased ghWithInput and ghWithInputRetried to
one mock, hiding which variant a write used; they are two mocks now,
and the happy path asserts the non-retrying variant is never touched.
- The Prepared interface's dead `name` field is gone.
46 files / 1476 assertions green; tsc --build and eslint clean.
|
||
|
|
09d818867e
|
fix(review): follow output language in Tip lines and saved reports (#8370)
* fix(review): follow output language in Tip lines and saved reports The /review skill's critical rule 2 already states that terminal output follows the user's output language preference, but three areas lacked explicit guidance, causing the model to output them in English even with a Chinese output language configured: - Follow-up Tip lines (e.g. "Tip: type post comments to ...") were specified as English templates with no translation instruction - The Step 8 saved report file used English section headings and prose - Step 6 labels (e.g. "Balanced review (effort: medium)") had no translation note Add explicit output-language guidance at each point of use, with a Chinese example for Tip lines and Chinese section heading examples for the saved report. Command keywords (post comments, fix these issues) stay verbatim since they are trigger phrases the user types back. * fix(review): disambiguate findings-artifact language boundary and complete low-effort translation notes (#8370) --------- Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
eea0a2b3b2
|
fix(github-channel): recover interrupted inbound tasks (#8306)
* fix(github-channel): recover interrupted inbound tasks * fix(github-channel): make inbound recovery bounded * test(github-channel): cover delivery-failure lifecycle and audit-hit recovery Add a direct test for the onTaskLifecycle failed/delivery -> reply_pending transition and a recovery test for the publication-audit match-and-remove path. Restore the blank line between the constructor and createInitialCursor. * fix(github-channel): preserve cancelled inbound tasks and fail closed on bookkeeping (#8306) * fix(github-channel): harden inbound task lifecycle against partial persistence failures (#8306) * fix(github-channel): close crash-window duplicates and make tests load-bearing (#8306) * test(github-channel): add recovery test for suppressed audit outcome (#8306) * fix(github-channel): persist errorCommentPosted after post, add review test coverage (#8306) --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
e967cc9037
|
docs: document compaction and image model selection (#8348)
* docs: document auxiliary model selection * docs: align model option labels * docs: clarify image model HTTPS requirement |
||
|
|
4338120100
|
feat(serve): resolve and report the daemon memory budget (#8245)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* feat(serve): resolve and report the daemon memory budget The daemon has no notion of how much memory it has. It samples its own RSS and heap every five seconds, and polls the primary ACP child's RSS, but there is no limit anywhere to divide those by: no cgroup read, no heap-size limit, no ratio, no `limits.*` memory field. Every number it reports is an absolute byte count with nothing to compare against, so "how close to exhaustion is this daemon" cannot be answered from `/daemon/status` at all. Resolve one set of figures at boot and report them. Configured and effective budgets are separate: the effective value is capped at resolved cgroup or host memory, so an operator passing a budget larger than the machine gets a denominator the machine can actually back, with the discrepancy visible rather than silently resolved. A derived budget below the documented minimum is reported as `insufficientMemory` rather than clamped upward, which would have invented capacity that does not exist — a 768 MB host would otherwise report a 1 GB budget and poison every ratio computed from it. `limits.memory` carries the static figures, including `legacyCeilingMb`: the ceiling an ACP child receives today with no budget involved, so the gap between current behavior and any future policy is measurable before that policy exists. `runtime.memory` carries live counts and the advisory per-child share at both the registered and the live child count. Nothing here sizes a child. Dividing the pool by a workspace count is not a sound policy on its own, and the advisory shares exist to show why: on a 32 GB host with 25 registered workspaces and only the preheated primary live, a registered-count divisor would cut that child from 16384 MB to 614 MB for memory no dormant workspace is holding, while the per-child floor still lets 25 children authorise more than the pool. Registration is not allocation; a real policy needs admission at spawn time keyed on concurrently live children, and it needs this data to be designed against. Applying such a share is also a compatibility change even with no refusals, since it alters child GC and OOM behavior — so it is not something this change should slip in under the heading of reporting. Refs #8182 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(serve): report honest memory counts and guard the registered share (#8245) * fix(serve): reuse workspace snapshots when counting active children Counting active ACP children from `listManaged()` is right — `list()` only returns entries in `active` state, so a workspace mid-drain, mid-replacement, or blocked still holds a live child that `list()` drops. But taking the count by calling `getDaemonStatusSnapshot()` again per managed runtime undoes the existing reuse of the primary bridge's snapshot, and `getDaemonStatusSnapshot` rebuilds the whole session array on every call. The second pass also reads the tree at a different instant than the rest of the response, so `activeAcpChildren` could disagree with the session and channel figures beside it. Reuse the snapshots already taken instead, keyed by bridge, and fall back to a fresh call only for a managed runtime the first pass missed — which is exactly the non-`active` entry the `listManaged()` change exists to catch. The reuse this restores was already guarded by a test asserting one snapshot call per bridge, but that test resolves no memory budget, and the second pass ran only on the budget path — so it stayed green while every production `/daemon/status` call did the work twice. Added a case that resolves a budget and asserts the same property; it fails against the previous commit with "expected 1 times, but got 2 times". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(serve): address review feedback on daemon memory budget (#8245) * fix(serve): import isValidMemoryBudgetMb in serve command (#8245) * fix(serve): address review feedback on daemon memory budget (#8245) * fix(serve): stub isChannelLive on serve test fake bridges (#8245) * fix(serve): address review feedback on daemon memory budget (#8245) - Make the stderr-gate test host-independent by pinning os.totalmem through a vi.mock toggle instead of reading the runner's cgroup - Add a spawn-path constant parity test enforcing that getAcpMemoryArgs and legacyChildCeilingMb agree on the fraction and cap, converting the comment-only invariant into a test - Narrow the mirror comment to name only the two constants that actually have spawn-path counterparts - Accept availableMemorySource through the resolveDaemonMemoryBudget seam so the constrained path is testable end-to-end - Report maxChildHeapMb alongside minChildHeapMb on the wire so clients can distinguish the 16 GB cap from a large host - Move the listManaged/list comment to the computation it describes - Add a cross-reference at the opts literal for the late-assigned daemonMemoryBudget field * fix(serve): address review feedback on daemon memory budget (#8245) * fix(serve): address review — split fraction constant, sharpen parity test, deduplicate error, populate bootstrap memory (#8245) * fix(serve): address review — document maxChildHeapMb, make parity test order-independent (#8245) * fix(serve): address review — split parity test into two files to avoid cold re-import timeout (#8245) * fix(serve): address review — correct session count, compatibility scope, bootstrap memory docs (#8245) * fix(serve): address review — align help text framing, document default cap, fix stale comment (#8245) * fix(serve): address review — add positive memory-budget validation test, correct activeAcpChildren docs (#8245) * fix(serve): address review — document childRssBytes stale tail after watcher detach (#8245) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: Qwen Autofix <qwen-autofix@users.noreply.github.com> Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
8d6d2ab56a
|
feat(cli): /summary supports custom export path (#8116)
* feat(cli): /summary supports custom export path (#8113) `/summary` now accepts an optional path argument, matching `/export`'s behavior. When a path is provided, the summary is saved there instead of the default `.qwen/PROJECT_SUMMARY.md`. - `/summary` → saves to `.qwen/PROJECT_SUMMARY.md` (unchanged) - `/summary docs/summary.md` → saves to `docs/summary.md` - `/summary /absolute/path/summary.md` → saves to absolute path - `/summary docs/` → saves to `docs/PROJECT_SUMMARY.md` If the path is a directory (existing or ending with `/`), the default filename `PROJECT_SUMMARY.md` is appended. Parent directories are created automatically. * fix(cli): summary custom path dir detection and i18n key (#8116) * test(cli): assert relative display path in summary tests (#8116) * fix(cli): summary path containment, early validation, and mkdir hardening (#8116) * fix(cli): defer summary mkdir to save time so failed generation leaves no empty dir (#8116) * fix(cli): normalize path separators in summary test and assert file content (#8116) * test(cli): assert directory permission mode in summary test (#8116) * fix(cli): resolve symlinks in summary path containment check (#8116) * fix(cli): reject broken symlinks escaping project root in /summary (#8116) * fix(cli): guard /summary overwrite and expand tilde in path (#8116) * fix(cli): re-validate appended default filename for symlink escape in /summary (#8116) * fix(cli): harden /summary symlink chain walk, file mode, and overwrite guard (#8116) * fix(cli): address review feedback on /summary custom path (#8116) - Fix CRLF false-negative in overwrite guard by normalizing line endings - Allow overwriting empty pre-created files (zero-length bypass) - Detect trailing separator on existing file and report clearly - Log chmod failures via debugLogger matching exportCommand convention - Add comment explaining mkdir mode asymmetry - Update docs: /summary usage table and custom-path welcome-back note - Add i18n key for trailing-separator error in all 9 locales - Add tests for CRLF, empty file, and trailing separator cases * fix(cli): address review feedback on /summary custom path (#8116) - Skip symlink-escape check for the default .qwen/ target so a symlinked .qwen/ directory (shared team config, overlay mounts) keeps working, and the check no longer runs after the LLM call - Re-run the overwrite guard immediately before writing to close the TOCTOU window across the slow generation step - Determine isDefaultTarget by comparing the resolved path against the default so `/summary .qwen/` gets the same 0o700 permissions - Only chmod 0o600 on file creation; preserve existing permissions on regeneration - Return empty content in interactive-mode errors to avoid double rendering (failInteractive already adds the error to history) - Tighten the overwrite-guard regex to require `**Update time**: ` after the Summary Metadata heading, preventing false positives - Fix the realpathNearestExisting comment to document the missing containment-during-walk guard vs export/stats copies - Add tests: symlink cycle, default target with symlinked .qwen, TOCTOU overwrite guard, explicit .qwen/ permissions, chmod preservation, interactive error content, regex false-positive * fix(cli): address review feedback on /summary custom path (#8116) * test(cli): cover post-LLM symlink re-check and interactive error UI (#8116) * fix(cli): address review feedback on /summary custom path (#8116) --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com> Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
74dc5547f1
|
docs: complete TUI keyboard shortcut reference (#8327) | ||
|
|
dbb0349351
|
feat(review): borrow recall, a fix loop, and a size-derived budget from Claude /review (#8315)
* feat(review): borrow recall, a fix loop, and size-derived budget from Claude /review Three changes, from a comparison of this skill against Claude Code's `/code-review`. The orchestration half of that comparison went the other way — nothing there has the worktree isolation, the transcript-backed coverage gate, or the single computed verdict — so what is borrowed is what it does better: how much it surfaces, what a finding *is*, and what a small diff costs. Recall ------ The Exclusion Criteria are a filter on what KIND of thing is a finding. Read as a confidence bar — which is how an agent under "silence is better than noise" reads them — they license dropping anything half-believed, and that drop is invisible: no later stage sees a candidate that was never filed. Every stage after the finders removes wrong findings; none can add a missing one. Each finder brief now carries the counterweight explicitly, and the Step 4 verifier deliberately does not get it. Code quality was one agent holding six unrelated checks — the shape this skill already refuses for invariant agents, on measured evidence (PR #6457: one agent with an eight-item checklist found 1 of 5 defects; the same model split three ways found all 5). Split into 3a reuse/duplication, 3b altitude/abstraction fit, 3c consistency/clarity. 12 -> 14 agents in 3A. Low was one undirected pass capped at 8, and its only alternative was a nine-subagent fan-out. It is now an angle rotation in one context — line-by-line, removed behaviour, language pitfalls, wrapper routing, reuse/dead code, sibling consistency, then a gap sweep — dedup-only, no re-judging, cap 10. Still zero subagents. --fix and findings as data -------------------------- `--fix` is `--comment` reflected and gated on the opposite target: `--comment` writes to a pull request, `--fix` writes to a working tree, so a PR review (whose tree is the ephemeral worktree Step 9 deletes) ignores it with a warning. An effective `--fix` floors the effort at medium — editing the user's files on an unverified finding is the same mistake as posting one. New `qwen review findings` canonicalizes the findings into a JSON artifact the terminal report, the saved report and the review JSON all read, instead of three transcriptions of one list. With `--outcomes` it merges the fixer's ledger and REFUSES one that does not account for every finding: a fixer that applies six of nine and reports six has not lied about any of them, it has silently shortened the list. Size-derived budget ------------------- New `plan.budget`, computed from srcDiffLines the way the topology gate is and recorded in the plan rather than passed as a flag, so every reader sees one number. Scopes the low tier's angle count and sweep, the Agent 8 ceiling (0 below 80 source lines — "one domain dominates" is a judgement, and one made about forty lines finds a dominant domain every time), and the verify shard width. It never scales a dimension away: that is the roster's answer and the roster reads effort. Not included: per-model prompt routing. Claude's table exists because it was measured per model family; shipping an invented mapping into this skill is the kind of change its own review would flag. Tests: 39 files, 1215 assertions. * docs(review): align counts and level descriptions left stale by the 3a/3b/3c split Round-1 self-review findings on this branch: - SKILL.md medium tier still named 'quality (Agent 3)'; the Step-1 low bullet and Step 3C heading hardcoded six angles though plan.budget scales them 3-6; and no fallback was stated for a plan written by an older CLI that carries no budget field (falls back to the flat pre-budget behaviour — more coverage, never less). - DESIGN.md still labelled the 12-agent roster '(current)', kept the ten-lens / crosses-twelve topology arithmetic beside the updated fourteen-agent copy, keyed the re-gating cost row to the 12-agent roster, and described low as one pass ≤8 and medium as unverified inline angles — both contradicting the SKILL.md this PR ships. The LLM-call-budget and Fork-Subagent sections were still summed for 12 agents. - findings.ts: validateFindings accepted outcome but dropped outcomeNote, so the canonical artifact did not round-trip — a skipped finding fed back through --input kept its outcome and lost its reason. Tests: 40 review files green, including two new round-trip cases. * docs(review): one id per finding across the cache ledger and the findings artifact The rebase onto #8218 left two id schemes for one finding: the incremental cache's cross-round ledger names findings R<round>-<n>, while the findings artifact accepted any unique id. Same defect, two names, and the outcome ledger and next round's report could no longer be joined. The artifact now uses the R-ids whenever the run writes the cache ledger. Conflict resolutions from the rebase itself: review.ts keeps both new subcommands (test-delta from main, findings from this branch); the documentation-parity check #8218 added to the old Agent 3 brief lands in 3c, the consistency slice that owns sibling-parity checks. * test(cli): add findings to the pinned review subcommand surface review.test.ts pins the exact subcommand list and sits one directory above the review/ glob the branch's local runs used, so the new findings subcommand never met it until CI. Ubuntu was the only matrix leg that ran. --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
8673151ebd
|
docs: document skill learning and live reload (#8298) | ||
|
|
ac4799feca
|
fix(cli): add ui.mouseTracking setting to restore right-click and URL clicks (#8198)
* fix(cli): add ui.mouseTracking setting to restore right-click and URL clicks
VP mode (default since 0.21.1) enables SGR mouse tracking (?1002h)
which captures ALL mouse events including right-click and left-click
on OSC 8 hyperlinks. The terminal can no longer handle these
natively — right-click context menus and clickable URL links stop
working.
Add a `ui.mouseTracking` boolean setting (default: true) that gates
SGR mouse tracking in useMouseEvents. When set to false, mouse
tracking is disabled entirely, restoring terminal-native right-click
and OSC 8 hyperlink handling. The trade-off is that mouse wheel
scrolling and scrollbar dragging in the VP viewport stop working;
keyboard scrolling (Up/Down/PgUp/PgDn) still works.
Users who need right-click or URL clicking can set:
"ui": { "mouseTracking": false }
Fixes the regression reported in #8003 (right-click broken) and
the follow-up report (URL link clicks broken) after 0.21.1.
* fix(cli): regenerate mouseTracking schema and clarify setting scope (#8198)
* fix(cli): scope ui.mouseTracking to the TUI and document it (#8198)
* fix(cli): handle hyperlink clicks in VP mode via onContentClick
When SGR mouse tracking is active (VP mode default), the terminal
cannot handle OSC 8 hyperlink clicks natively. Add an onContentClick
callback to ScrollableList that fires on left-press in non-scrollbar
areas. MainContent implements this callback by extracting hyperlink
regions from the clicked item's text (parsing markdown links and
bare URLs with visual column tracking) and opening the URL at the
click position via the platform's open/xdg-open/start command.
This allows URL clicks to work in VP mode without disabling mouse
tracking, so mouse wheel scrolling and scrollbar dragging continue
to work simultaneously.
New file: packages/cli/src/ui/utils/hyperlink-click.ts
- extractHyperlinkRegions(): parses markdown links and bare URLs
from raw text, computing visual column positions
- findUrlAtColumn(): maps a click column to a URL
- openUrl(): opens a URL via the platform's default handler
* fix(cli): remove broken hyperlink-click feature from VP mode (#8198)
Remove the hyperlink-click feature (hyperlink-click.ts, handleContentClick,
onContentClick) which had three critical defects identified in review:
1. Command injection on Windows: openUrl used child_process.exec with shell
string interpolation, where single-quote escaping is ineffective against
cmd.exe metacharacters (&, |, ^, ;).
2. Non-functional coordinate mapping: handleContentClick used raw 1-based
SGR terminal screen coordinates directly as item indices into
allVirtualItems, without the scroll-offset and frame-anchor transforms
that every other mouse consumer applies.
3. Wrong visual-column accounting: extractHyperlinkRegions counted raw
markdown characters (including **bold**, *italic*, ~~strikethrough~~
markers) that InlineMarkdownRenderer strips, causing column drift.
The ui.mouseTracking setting (the PR's core purpose) is unaffected and
remains fully functional. A properly designed click-to-open feature can
be added in a follow-up with correct coordinate transforms, renderer-aware
column parsing, and openBrowserSecurely for safe URL launching.
* docs(cli): cross-reference ui.mouseTracking in useTerminalBuffer descriptions (#8198)
* fix(cli): document mouseTracking tradeoffs and hide showScrollbar in serve mode (#8198)
* docs(cli): clarify mouseTracking tradeoffs in shortcuts and settings (#8198)
Address review feedback: note that mouse-wheel scrolling requires ui.mouseTracking in the keyboard-shortcuts table, the tmux trackpad section, and troubleshooting; add the missing ui.showScrollbar row to the settings reference; and pin the mouseTracking schema fields (requiresRestart: false) in settingsSchema.test.ts.
* fix(cli): make mouseTracking restart-scoped and gate mouse affordances (#8198)
ui.mouseTracking declared requiresRestart: false but nothing propagates a
settings write to the refcounted SGR mouse mode: the SettingsContext value
is a single LoadedSettings created outside React with no change emitter, so
a subscriber that never re-renders (e.g. a memoized completed thought block)
keeps ?1002h active and right-click stays broken even with the setting off.
Mark it requiresRestart: true, matching ui.useTerminalBuffer, and drop the
runtime-toggle test whose own comment admitted it did not prove the flip
lands.
Add a shared useMouseTrackingEnabled hook and route the useMouseEvents gate
and the mouse-dependent affordances through it — the history-item "click to
expand" hint, the selection-list hover controller, and prompt
click-to-position — so they read one source and no longer advertise a dead
interaction when mouseTracking is disabled.
* test(cli): add component-level mouseTracking affordance gate tests (#8198)
* fix(cli): address review feedback on mouseTracking PR (#8198)
- Rewrite BaseSelectionList mouse test to mock RowMouseController and
assert mount/unmount directly, pinning the component-level gate
instead of the downstream useMouseEvents escape writes
- Add troubleshooting entry for right-click/links/selection symptoms
- Add useTerminalBuffer pairing advice to settings.md mouseTracking row
---------
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
|
||
|
|
6f8ad2b4a5
|
feat(review): Include CLI version in attribution (#8294)
* feat(review): Include CLI version in attribution Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(review): Decouple footer test from package version Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
e569734a1e
|
feat(skills): add auto-skill curator (#7846)
* feat(skills): add auto-skill curator * fix(i18n): translate /curator command descriptions for zh and zh-TW The strict-parity locale coverage test failed because the new /curator command and its status/run/restore subcommands fell back to English descriptions in zh-CN and zh-TW. Add Simplified and Traditional Chinese translations for the curator command descriptions and its user-facing output strings. * fix(i18n): add English source keys for /curator command The strict-parity i18n check failed because zh and zh-TW defined the new /curator command keys while en.js (the source of truth) did not, producing extra-key parity errors. Add the matching English source entries so all supported locales share the same key set. * fix(skills): align curator lifecycle safeguards * fix(skills): harden curator trust and name guards * fix(skills): guard curator mutations by workspace trust * test(skills): cover curator stale-to-active reactivation path * test(skills): cover curator rollback and restore-collision paths Add coverage for two previously untested error paths in the auto-skill curator: - restoreArchivedAutoSkill refusing to overwrite an existing active directory, leaving both the reused directory and the archived copy intact. - runAutoSkillCurator rolling back an archive rename when the post-move state persistence fails, returning the skill to the live library and leaving nothing stranded in the archive (new isolated test file that mocks atomicWriteJSON to fail once). * test(cli): cover curator command errors and stacked auto-skill usage - Add mockRejectedValue error-path tests for the /curator status, run, restore, and pin commands, asserting each surfaces messageType 'error' and that a failed run/restore skips skill-discovery refresh. - Add positive stacked auto-skill tests to both the non-interactive and interactive slash-command paths, asserting recordAutoSkillUsage is called once per successful stacked skill carrying project skillDetail. * fix(skills): reject control-byte auto-skill directory names isManagedDirectoryName only checked the auto-skill- prefix and basename, so a crafted directory whose name embeds ANSI/control bytes was treated as a managed skill and its name printed verbatim by the non-interactive /curator output (which, unlike the TUI, does not run escapeAnsiCtrlCodes), enabling terminal control-sequence injection from a cloned repo. Require the directory name to match SKILL_NAME_PATTERN. A managed dir is always auto-skill-<name> where <name> passes validateSkillName and the prefix chars are within the same charset, so this never rejects a legitimately generated directory (including Unicode skill names, which an ASCII-only guard would wrongly drop) while excluding ESC/control bytes. Add a regression test covering a crafted directory with a valid manifest name so only the directory-name guard can exclude it. * fix(skills): guard curator state reads and clarify restore errors Align the curator state read path with the noFollow/lstat guards every write already uses: refuse a symlinked or non-regular-file state file (which could otherwise be followed to an external path, /dev/zero, or a FIFO, causing OOM or a boot hang in untrusted workspaces) and cap the read size. Also distinguish a present-but-ineligible archived skill from a missing one in restore error messages. * fix(skills): close curator TOCTOU reads and preserve seeding baseline Address review feedback on the auto-skill curator: - readManagedSkill previously read the manifest via Promise.all([lstat, lstat, readFile]); the readFile ran concurrently with the lstat guards, so a symlinked SKILL.md pointing at /dev/zero could start an unbounded read before the guard rejected it. Read the manifest with O_NOFOLLOW + an fstat size bound instead (shared readRegularFileNoFollow helper), refusing symlinks atomically and bounding the read. - readState had the same lstat->readFile TOCTOU window; the O_NOFOLLOW read closes it while keeping the existing friendly error messages. - First-run seeding overwrote firstSeenAt/lastActivityAt with now even when recordAutoSkillUsage had already created a record, resetting the inactivity clock. Preserve an existing baseline (like useCount/pinned/ lastUsedAt), while a brand-new skill still gets a fresh now baseline. Adds regression tests for the seeding-baseline preservation and for refusing a symlinked manifest. * fix(core): harden auto-skill curator per review feedback (#7846) - Resolve node:fs constants lazily so importing the curator does not crash tests that mock node:fs without a constants export. - Preserve the original error via cause when a rollback also fails. - Apply the skill-name charset guard to archived directory names reserved in the review-agent task prompt. * test(core): cover curator restore rollback and re-read guard (#7846) * fix(core): record auto-skill usage on re-invocation (#7846) * fix(skills): preserve curator read failures * fix(skills): harden curator lifecycle guards * test(skills): cover curator safety paths * fix(skills): address curator review findings (#7846) - Ignore future manifest mtimes in lastActivityMs so a bogus timestamp cannot make a skill permanently un-curatable - Skip archived status entries whose directory is also live, preventing contradictory double-listing in /curator status - Check the weekly interval before acquiring the cross-process lock in maybeRunAutoSkillCurator so most boots skip the lock entirely - Use handle.readFile() instead of a single handle.read() to avoid silent truncation on short reads * fix(cli): localize curator messages * test(skills): cover curator usage safeguards * fix(skills): address curator review feedback (#7846) - Isolate per-skill rename failures so a transient error no longer aborts the whole pass and prevents state persistence (boot-loop fix) - Make usage recording fire-and-forget (void instead of await) since it is already best-effort and nothing consumes the result - Skip state file creation when no auto-skills exist - Prune dead records whose directory exists in neither root - Sanitize user-supplied directory names in error messages (JSON.stringify) to close the ANSI control-sequence echo path - Split reserved skill names into active/archived lists in the review-agent task prompt - Make collision output actionable with remediation guidance - Fix rollbackMoves mutating its argument (moved.reverse → copy) - Add null guard to isMissing for non-object rejections - Add locale keys for skippedErrors output (9 locales) --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> Co-authored-by: Qwen Autofix <qwen-autofix@users.noreply.github.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
907c7dea70
|
fix(cli): stabilize thinking block height, replace transcript overlay with inline Ctrl+O toggle (#8077)
* fix(cli): hide streaming thinking preview, rebind Ctrl+O to inline fullDetail toggle The streaming thinking block showed a 4-line preview that varied in height due to empty lines in the model's reasoning output, causing constant page reflow and flicker during generation. Changes: - ThinkBody now renders nothing when collapsed (both streaming and committed), keeping the block at a stable 1-line header height. - Ctrl+O now toggles inline fullDetail mode (like Claude Code): all thinking blocks, tool groups, and tool results expand/collapse in the main conversation view — no alternate-screen overlay. - Alt+T preserved as hidden shortcut (same toggle, not shown in UI). - MainContent passes fullDetail to HistoryItemDisplay via the existing ThoughtExpandedContext, so the toggle works in both VP and Static rendering paths. - Removed TranscriptView overlay rendering, transcriptItems memo, StreamingContext import, and EMPTY_HISTORY_ITEMS constant. - Removed dead code: tailVisualLines, grow-only height tracker, MAX_STREAMING_THINKING_VISUAL_LINES, openTranscript callback. * refactor(cli): remove orphaned transcript full-detail infrastructure (#8077) * fix(cli): update Ctrl+O help text and add thinking-expansion integration test (#8077) * fix(cli): update docs, help text, and remove transcript dead code (#8077) * fix(cli): strengthen Ctrl+O full-detail tests and refresh stale docs (#8077) * fix(cli): address review feedback on test isolation, dead i18n keys, and missing negative case (#8077) * fix(cli): add M1 mutation-killing assertion to Ctrl+O test (#8077) The existing test only asserted clearTerminal (a refreshStatic side effect) and never verified the setThoughtExpanded state flip. Under the vi.mock('ink') harness the mocked App never re-renders from a directly-called handler, so a behavioural allExpanded assertion is not possible. Add a structural guard on the handler source that fails when setThoughtExpanded is removed (mutation M1 verified). * fix(cli): strengthen Ctrl+O toggle assertion and document non-VP redraw (#8077) Tighten the structural M1 assertion from .toContain to a regex that matches the (prev) => !prev updater pattern, catching mutations like (prev) => true that the old check would miss. Document the non-VP scrollback redraw in keyboard-shortcuts.md per maintainer request. --------- Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com> Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |