mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-03 13:07:56 +00:00
454 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
02b8b0108c
|
feat(web-shell): show a Channel event timeline (#16106)
* feat(web-shell): show a Channel event timeline Channels reported what they did only through stderr, the publication audit, and the inbound task store, none of which the daemon could read. An operator wondering why a GitHub comment got no reply had to read the daemon log. Every Channel now appends one bounded journal per instance: the inbound messages it accepted, the ones it skipped and the machine reason for each skip, publication outcomes, inbound task transitions, and poll results. The journal holds routing decisions and identifiers only, never message text; it rotates at 1 MiB to a single backup, and a write failure is swallowed into one stderr line so diagnostics cannot break message handling. The daemon serves the journal through GET /workspace/channels/events and its workspace-qualified form, with limit, type, channel, and since filters, a 32-file and 2 MiB read bound, and the existing trust and client checks. The channel parameter repeats once per instance name because a Channel name may legally contain a comma. The Web Shell gains a Channel events page and sidebar entry that render the cross-Channel timeline for the resolved workspace, filtered by Channel and event type and refreshed every five seconds, gated by channel_management. Docs: the design in English and Chinese, the serve protocol, and the Channel user guide. Channel tests isolate QWEN_HOME so journals never land in a real home directory. * fix(web-shell): scope Channel events to the selected workspace Review found four correctness gaps in the event timeline. The page read the provider's daemon-wide primary cwd, so a Channel started in a registered secondary workspace could never be inspected. It now offers the same workspace selection as the management page and passes the selected cwd to both hooks; changing the workspace resets the Channel filter. The poll loop captured the adapter's detail before the poll ran, so the first success had no counters and every later one carried the previous poll's counters against the current timestamp. The field is now cleared before the poll and consumed after it settles, on both the success and failure paths. The 32-file read cap was applied before the Channel filter, which made an explicitly requested, quieter Channel unreachable once busier peers had newer files. Candidate files are now narrowed by the requested identities first. Channel identities were truncated to 80 code points while the management API accepts 255 UTF-8 bytes, so longer legal names never matched their own filter and names sharing a prefix became indistinguishable. The identity now keeps its full legal length; only the UI shortens it for display. Each fix has a regression test that was mutation-checked against the previous behavior. Design (both languages) and the Channel user guide now describe the workspace selection, the poll-detail timing, the filter-before-cap ordering, and the identity limit. * fix(web-shell): mask Channel events cached for another query The workspace selector changes the query without remounting the hook, and the underlying resource keeps its previous data while a request is in flight or after it fails. Selecting a second workspace therefore showed the first workspace's rows under the new selector until the new request succeeded, and kept them when it failed — including their Channel names in the filter list. Each snapshot is now tagged with the query that produced it (workspace, Channel, types, limit) and mismatched data is masked, the same way the Channel management hook already does. Two hook-level regressions drive the real hook and resource with a pending and a rejected second-workspace request; both fail without the tag. * fix(web-shell): keep Channel event reads off the daemon's critical path A verification round measured the journal read at the documented cap: one page poll took ~860 ms of synchronous I/O, an unrelated /health request waited ~790 ms behind it, and four concurrent reads serialized to 3.4 s. The route runs on the daemon that also serves sessions, so a diagnostic page must not be able to stall it. The read is now asynchronous and chunked (64 KiB awaits) under tighter bounds: 256 KiB per journal file, 4 MiB per request, 32 files, freshest first. On the same 32-file / 32 MiB fixture the read is ~175 ms, a bystander /health stays at 6 ms p50 against a 2 ms baseline, and four concurrent reads finish in ~630 ms. Two reporting gaps go with it. Anything the bounds clip — dropped files or an exhausted byte budget — now sets `truncated`, so the page cannot show a timeline that silently omits whole Channels. And a scope directory that cannot be read propagates instead of returning an empty list, so the route answers 5xx rather than claiming the workspace's Channels did nothing; a missing directory still means "nothing written yet". The page also stops polling while its tab is hidden and reloads when it becomes visible, so a forgotten tab no longer reads journals every 5 s. Tests pin the two behaviors the wire depended on but the suite did not: reading events that survive only in the rotated file, and clamping a limit above the maximum. Both new mutations are killed, as are the truncation, unreadable-directory, and hidden-tab guards; a bystander-timer test pins that the read yields to the event loop. * fix(web-shell): report a clipped journal tail as truncated The per-file read window introduced with the asynchronous read is 256 KiB, but a journal grows to 1 MiB before it rotates. `readTail` skipped the head of a taller file without telling anyone, so a single busy Channel lost its own older events while the response still claimed `truncated: false` — the exact completeness contract the previous commit set out to fix. A 1 MiB journal of 1020 records answered 255 events with `truncated: false` at `limit=500`. `readTail` now reports whether it started mid-file and the reader folds that into `truncated`. The request budget is also spent in whole tail windows instead of a final partial one, so its flag keeps the precise meaning "whole journals were left out". Three tests close the gaps this exposed: a journal taller than the tail window must report truncation even when the returned rows fit the limit; the budget fixture now uses twenty full-window journals whose combined rows stay under the record limit, so only the budget can explain the flag; and a journal file that cannot be read must fail loudly, like the directory case already does. All four previously surviving mutants — tail flag, budget flag, budget debit, swallowed file-read errors — are now killed. * fix(web-shell): bound Channel event read retention The events read parsed and retained every matching record across all journals before applying `limit`, so an all-Channels poll on a busy workspace held far more than the rows it returns. Trim the accumulator to the newest `limit` once it passes four times that. A dropped record is older than every kept one and the sort is stable, so the answer does not change: a 60-scenario differential run against the previous behaviour produced byte-identical output. Trimming counts as hitting the record limit, so it sets `truncated` too. Closes the remaining non-blocking review items as well. The provider workspace action declares the full query type it already forwards, so the interface no longer suggests that path cannot filter by Channel. The new UI strings use the established 频道 label rather than introducing a second word for Channel beside the existing sidebar entry; the design docs keep 渠道, which is what every other zh-CN design uses. The docs now state the page's own request size (200) apart from the route default (100), and the `truncated` comments describe every reason it can be set instead of only the record limit. The NUL query-key separator is documented as collision-free: instance names reject control characters, journal names are sanitized, and a path cannot contain NUL. * fix(channels): read a journal that fits the budget and keep aligned records Reserving a full 256 KiB window per journal left a small journal unread when the remainder was under one window, even when it covered that journal many times over — coverage lost in exchange for a flag that was already truthful. Reserve what the journal actually needs instead, using the size the enumeration already stats, so the budget flag still means "whole journals were left out" and a 200-byte journal is no longer skipped beside seventeen full ones. A tail window that landed exactly on a line boundary also dropped one complete record, because the first line was discarded whenever the window started mid-file. Read one leading byte and drop that line only when the byte is not a newline. Folding the byte into the first chunk keeps the read to one awaited call per chunk, which is what lets the daemon serve other requests between them — now pinned by a test that compares the event-loop turns a tall read takes against a short one over the same six journals. A synchronous chunk read fails it. Also records why an unparseable instant cannot satisfy `since` while an unfiltered read still returns it, and pins both halves. * fix(channels): collapse repeated reason-filter skips into a counted row A real end-to-end run — the actual GithubAdapter over HTTP against a local GitHub-compatible API, a real agent turn, the page in a browser — exposed an amplification the seeded-journal runs could not. The poll cursor does not advance while an inbound task is recoverable, so a task that failed and is still retryable makes the provider return the same notifications on every poll, and filtered notifications are skipped before any dedupe. Each poll therefore re-journaled the whole batch: with a 2 s poll and 300 filtered notifications the journal rotated and pushed the accepted/posted rows out of the page's 200-row window. Journal at most 20 reason_filter rows per poll and collapse the remainder into one row carrying detail.filtered, which the page renders as a count in both languages. The budget is per poll, so a genuinely new batch is still described in full. Measured against the real adapter: 15 polls served 4,500 notifications and wrote 315 rows instead of 4,500, without rotating. Against a stub that honours both `since` and GitHub's read-state semantics, the redelivery stops once the batch is marked read and a new batch gets a fresh budget. The design docs record the mechanism precisely, including that an in-flight turn cannot cause it because the poll loop awaits pollOnce. * fix(web-shell): keep one Channel event polling loop across visibility restores Restoring tab visibility while a journal request was still pending started a second run. Clearing the timer handle cannot cancel an in-flight run, so both runs settled, both called schedule(), and only the last timer handle was kept — the orphan kept polling forever. Ordinary tab switches on a slow response therefore multiplied journal reads permanently: one overlapping hide/show cycle left two requests per interval, three cycles left four. Guard the cycle with an in-flight flag so a restore during a pending request lets that request finish and reschedule instead of starting a second loop. Both read paths are time-bounded (the SDK's 30 s fetch default and the action timeout), so the guard cannot stall polling. The regression test holds every request pending, overlaps three hide/show cycles, and asserts one request per interval afterwards; removing the guard reproduces the reported four calls. |
||
|
|
ae7901552e
|
fix(core): settle the loop reminder delivery at the model-acceptance boundary (#15559)
* fix(core): settle the loop reminder delivery at the model-acceptance boundary takeLoopReminder() cleared the pending map and logged the 'reminded' telemetry while the ToolResult request was only being assembled. Between the take and turn.run(), the send still awaits microcompaction and memory recall and yields pending goal events, so a consumer that stops the generator there loses the streak's warning (dropPendingReminders only unmarks still-pending keys) while telemetry credits a delivery the model never saw. The delivery is now only prepared at the take: the client commits it when the first model-accepted event arrives and abandons it — restoring the pending entries — in the send's finally when the request never reached the model. A full reset drops a prepared delivery so a late abandon cannot leak a stale warning into a new prompt. The agent-core round loop keeps its take-time accounting with an explicit commit since it has no consumer-visible suspension between the take and the model call. Fixes #15537 * fix(core): defer the agent-core loop reminder commit to model acceptance * fix(core): drop the ineffective pre-round reminder abandons The three abandonLoopReminderDelivery() calls at the abort/maxTurns/time checks could not change anything: each is followed by a break out of the round loop, the detector is method-local, and abandon only moves entries between two private maps without logging. They made the take-site comment and the maxTurns control read as if they were what keeps `reminded` truthful, so drop them and say what the control actually pins — no round crosses a model-acceptance boundary, so nothing commits. Also, from the same review round: - abandon's doc block named the wrong function. A retry drops the streak through resetToolCallCount(), which deletes the three hard-coded keys; dropPendingReminders() cannot reach entries already taken out of the pending map. - client.test.ts asserted not.toHaveBeenCalled() immediately after mockClear(), which cannot fail. Compare the call count with the value captured before the send instead, so the "suspended before turn.run" premise is actually pinned. - cover the multi-entry path: commit credits every entry of a delivery and abandon restores all of them, not just the first. - telemetry.md and both languages of the loop-protection design doc now say `reminded` is logged at model acceptance, and the "sent, never accepted, credited on a later send" case is added to the enumeration of not-counted cases. --------- Co-authored-by: suzhiguo7 <suzhiguo7@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
6e49388c4a
|
docs(serve): refresh daemon usage guidance (#16056)
Replace obsolete alpha-era deployment and persistence limits with current behavior. Document authenticated background startup, token sidecar lifecycle, and service-manager choices. Co-authored-by: verify <verify@local> |
||
|
|
6bef5a01c6
|
feat(web-shell): add "Add to Split View" to session menus (R1 fixes for #14878) (#15956)
* feat(web-shell): add sessions to split view from the sidebar * fix(web-shell): validate split targets and retain pending additions * fix(web-shell): report transient split-classification failures per session (#14775) * fix(web-shell): harden the first split open: race, per-id memo, pending mirror, failure notice (#14775) * fix(web-shell): disable no-op split adds, clarify the capacity hint, document split (#14775) * fix(web-shell): fix split classification type errors (#14775) * test(web-shell): fix split pending-navigation and archived-offer test refs (#14775) * style(web-shell): prettier-format the split classification memo type (#14775) * fix(web-shell): adapt controlled pane reveal to prop-derived pane set The main merge (#14577) derives paneIds from the sessionIds prop in controlled mode, so the reveal effect could no longer sync local state (setPaneIds does not exist) and its render-time ref check was dead: the ref is current by the time the effect runs. Track the previous commit in an effect-time ref instead. Also restore the test-block closer the merge dropped from SplitView.test.tsx. * Fix split-persist e2e spec: assert disabled state for already-open session (bot R2 blocker) The first smoke test clicked 'Add to Split View' on Session A after it was already a split pane. The R1-10 gate disables that menu item with the title 'This session is already open in Split View' (sidebar splitAlreadyOpen), so Playwright retried a click on a disabled item. Replace the click with toBeDisabled() + title assertions, reusing the shape of the six-pane spec in the same file. The no-op guarantees (pane count, draft, DOM persistence) are kept as-is. --------- Co-authored-by: luozongmin <994718917@qq.com> Co-authored-by: Сергей Панченко <s.panchenko@mcplat.ru> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
86f1338b86
|
feat(serve): batch workspace session live-state snapshots (#12513)
* feat(serve): batch workspace session live-state snapshots * test(serve): include batch live-state capability in integration baseline * fix(serve): close review gaps in the batch session live-state route (#12513) Index POST /sessions/live-state in the REST reference's persisted-catalog row so the docs contract sees the protocol section, give the batch route the single-workspace route's server-side records (routing-failure warn on untrusted members, daemonLog error on unexpected bridge failures), make the mid-batch disconnect guard observable with an AbortController and a macrotask yield between members, and move the duplicated member-failure, generation-currency, and bound literals into workspace-route-runtime for both plural batch routes. Six tests pin the new witnesses. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): align live-state scope docs and pin batch route guards (#12513) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): pin grouped REST doc rows against bare SDK names and column shifts Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): fail closed on grouped REST doc rows and ambiguous SDK qualifiers Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(serve): pin documented live-state batch bounds against silent drift (#12513) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): isolate the suite from the operator's settings home (#12513) The autofix verification gate runs the packages/cli suite with the host's real HOME on a machine shared with other jobs. A malformed $HOME/.qwen/settings.json there (observed content: "X") makes every operator-settings reader fail closed at once — readOperatorSettingsScopes is designed to refuse a corrupted policy file — and one such file failed 408 tests across 15 files in the measured gate rejection. Pin a private empty QWEN_HOME per test-file process in the suite setup when the environment did not select one, following the file's existing doctrine for operator-owned environment. Tests that exercise QWEN_HOME set, stub, or delete it themselves. settings.test.ts keys every user-settings and .env path off a mocked os.homedir, so it now deletes an ambient QWEN_HOME explicitly; with any ambient value set, 13 of its tests fail regardless of this change. * test(cli): harden the QWEN_HOME suite pin and batch maxima test (#12513) - Pin on truthiness so an ambient empty QWEN_HOME no longer falls through to $HOME/.qwen, matching every production reader. - Sweep the pinned home from afterAll instead of an 'exit' listener that vitest workers never fire (51 leaked dirs on src/config -> 0). - Restore QWEN_DEBUG_LOG_FILE around loadCliConfig tests so the debug logger's fire-and-forget writes stop racing the sweep. - Witness that the suite never resolves settings from the ambient $HOME/.qwen, and assert member shape in the 20-selector maxima half so twenty failed members cannot read as twenty snapshots. * test(cli): keep AppContainer store work off the pinned per-file QWEN_HOME (#12513) * test(managed-agent): settle fresh group leaders before age-judged sweeps (#12513) A just-forked process can read in ps with procps's negative-elapsed wraparound (e.g. 441077234-00:18:40), which parsePsElapsed deliberately leaves undatable. An undatable leader judges every worker/group identity 'unknown', so an unwitnessed sweep conservatively holds the live process and rejects unproven — flaking the real-process sweep tests within milliseconds of the spawn. settledGroupTable now also waits for the leader's row to be datable, and the four sweep sites that judged a fresh process without the guard use it. * fix(cli): keep the QWEN_HOME test pin unconditional and alive for the process lifetime (#12513) The conditional pin honored an ambient QWEN_HOME, so agent-gate vitest runs read and wrote the operator's live settings home; the teardown sweep then deleted the live pin while un-awaited extension-store work was still reading it, turning Test (ubuntu-latest) red on ENOENT. Pin unconditionally, keep the pin for the process lifetime, reap stale pins by age at setup, await the floated extensions-list handler, and add teardown, pin, reaper, and memory-only-contract witnesses. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): keep the QWEN_HOME teardown witness inside test-setup.ts (#12513) The previous commit registered the pin teardown witness as a second setupFiles entry in packages/cli/vitest.config.ts, which deterministic verification rejects as CI/verification machinery outside the PR footprint. Revert vitest.config.ts and fold the witness into test-setup.ts itself: vitest runs root-level afterAll hooks in reverse registration order, so the hook registered immediately after the pin runs last among this file's root hooks and still turns every file red if a teardown sweep of the live pin is reintroduced below it (mutation-probed). The separate witness setup file is deleted. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(integration): raise the installed distribution case ceiling The tui-parity installed-distribution darwin-x64 leg failed on the npx-lifecycle-enabled no-relaunch-fallback case with "Test timed out in 120000ms" (run 40537979533, job 122316223603). The five preceding cases averaged ~117s on that host, right at the ceiling: startup alone can spend the whole 90s ready-prompt budget there (#15677) and the /about retry window is another 90s, so 120s cannot contain a healthy case. Raise the per-case ceiling to 240s and keep the budget helper's note in sync. --------- Co-authored-by: 西岐 <fengqi.mfq@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-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> Co-authored-by: qwen-code-autofix <qwen-code-autofix@users.noreply.github.com> Co-authored-by: qwen-code-autofix <qwen-code-autofix@localhost> Co-authored-by: qwen-code-autofix[bot] <qwen-code-autofix[bot]@users.noreply.github.com> Co-authored-by: Qwen Autofix <autofix@qwen-code.local> |
||
|
|
dba7d065ce
|
feat(web-shell): expose archived export and restore inline (#15872)
* feat(web-shell): expose archived export and restore inline * test(web-shell): use inline restore in standalone E2E |
||
|
|
0976826090
|
fix(web-shell): align no-workspace category header with peers (#15897)
* fix(web-shell): align no-workspace category header with peers * test(web-shell): preserve archived no-workspace header presentation * docs(web-shell): show sidebar category header comparison |
||
|
|
a1f567a64f
|
feat(web-shell): extend one-click update coverage and surface manual steps (#15849)
* feat(web-shell): extend one-click update coverage and surface manual steps * fix(web-shell): keep More menu open for the manual update panel * fix(web-shell): shape-neutral update guidance and drawer overlay exemptions |
||
|
|
64e3aa7a18
|
fix(web-shell): standardize workspace and session display copy (#15315)
* fix(web-shell): standardize workspace and session display copy * test(web-shell): sync workspace menu text expectations * docs(web-shell): clarify the scope of display terminology * docs(web-shell): align remaining display-label references * test(web-shell): match the renamed new-session action --------- Co-authored-by: 大知 <dekun.dk@alibaba-inc.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> |
||
|
|
3131fcc230
|
feat(web-shell): allow embedding hosts to pick workspace directories (#15296)
* feat(web-shell): allow host workspace directory pickers * docs(web-shell): add host directory picker demo screenshots * fix(web-shell): preserve focus across host picker handoff --------- Co-authored-by: Jiankai Xiao <jkxiao1997@gmail.com> |
||
|
|
0ecdf0342d
|
fix(serve): revalidate skill snapshots in the background (#14890)
* fix(serve): revalidate skill snapshots in the background * test(serve): include snapshot metadata in skills route response * fix(serve): preserve foreground skill observations * fix(serve): publish valid slow skill observations * fix(serve): retain skill snapshots when observations fail * fix(serve): report failed skill catalogs consistently (#14890) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): trace discarded warm skills errors and pin observation age (#14890) * fix(cli): tighten skill snapshot cache and settings lock scopes Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): correct preheat contract and skills cache invalidation (#14890) * fix(serve): evict invalidated skills enumeration and correct route docs (#14890) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): preserve skill toggle order and retained catalog fallback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): require an enumerated skill catalog for deletion (#14890) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: claudeethan <claudecodeethan@gmail.com> Co-authored-by: Ethan Ye <ethanye@U-463KXYWL-1938.local> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
9a41afe0d4
|
feat: add persistent session Workflow mode (DW-04A) (#15422)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Lint & Static (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / web-shell E2E Smoke (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (no-AK, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Blocked by required conditions
E2E Tests / Build for E2E (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 1/2 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 2/2 (push) Blocked by required conditions
E2E Tests / E2E Interactive - OpenTUI renderer (bun) (push) Blocked by required conditions
E2E Tests / channel-plugin E2E (nightly) (push) Blocked by required conditions
E2E Tests / cron-interactive E2E (nightly) (push) Blocked by required conditions
E2E Tests / web-shell Browser Regression (push) Waiting to run
Managed Agent gate / Select the Managed Agent tier (push) Waiting to run
Managed Agent gate / SDK Java () (push) Blocked by required conditions
Managed Agent gate / Managed Agent gate (push) Blocked by required conditions
pnpm Worktree Smoke / Install (macos-latest) (push) Waiting to run
pnpm Worktree Smoke / Install (ubuntu-latest) (push) Waiting to run
pnpm Worktree Smoke / Install (windows-latest) (push) Waiting to run
Verify Landlock helper / arm64 (push) Waiting to run
Verify Landlock helper / x64 (push) Waiting to run
Verify Landlock helper / Native Linux CLI boundary (push) Waiting to run
Verify Landlock helper / Native Windows settings and streams (push) Waiting to run
* feat(core): persist session Workflow mode and dispatch reminders * feat(sdk): expose session Workflow mode through native and daemon controls * feat(cli): add Workflow mode controls and end-to-end coverage * fix(cli): preserve pasted text when recalling prompt history * fix(sdk): complete Workflow mode routing and owner-safe replies * test: verify Workflow mode recording and Mod permission boundaries * test(sdk): select the secondary workspace ACP mount * test(cli): assert headless Workflow mode eligibility * fix(workflow): enforce owner trust for prompt mode writes * fix(workflow): keep daemon write policy out of MCP metadata * fix(workflow): preserve retry and restore state across lifecycle failures * fix(workflow): align ACP retry types with shared loop * test(workflow): isolate ACP cadence from followup suggestions * fix(sdk): close query after initialization failure * fix(cli): serialize workflow mode writes with session switches * fix(sdk): keep mode errors async and reject pending reinitialization * fix(cli): filter workflow mode from SSH raw session state * fix(cli): preserve workflow mode startup failures * fix(acp): refresh restored session config options * fix(core): rearm workflow notices after cancellation * fix(core): rearm workflow notices after failed switches --------- Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com> |
||
|
|
6f53133d6d
|
feat(web-shell): stream resumable BTW answers (#15709)
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com> |
||
|
|
a92b4efe72
|
feat(web-shell): show /autofix-pr watches and stop them from the Web Shell (#15668)
* feat(web-shell): show /autofix-pr watches and stop them from the Web Shell The daemon serves the /autofix-pr watches of a workspace's clone, as `qwen autofix-pr status --no-work` lists them: GET /autofix-pr/watches for the primary workspace and /workspaces/:workspace/autofix-pr/watches for a registered, trusted one, advertised as `autofix_pr_watches`. It finds the clone's main worktree with `git worktree list` and reads the watches' files only; runStatus takes that root and no session instead of the daemon's own directory and environment. The Web Shell shows each watch on the card of its scheduled task and on a new PR watches page in the sidebar: the PR, where the watch is (watching, fixing in the background, a fix left behind, not checked recently, the PR merged or closed, stopped), its pushes, its last check and round, and the items waiting for a person. For a watch run by a conversation of this Web Shell, Open session goes there and Stop watch sends `/autofix-pr stop <n>` to it, as a scheduled task's manual run sends its prompt. Docs: design Section 6.15 (EN and zh-CN), the user guide, and the serve protocol. * fix(web-shell): run no git for the watch listing, and match the gate's stale rule - The daemon's listing runs no git command but the one that finds the clone: runStatus takes `git: false`, which skips the gitfile and submodule checks as well as the worktree scan, and the view leaves out the worktree path, gitTrusted, worktreeWork and pushTarget. It names the clone (`root`). - State files, gate cursors and fix markers are read without following a link and without blocking, judged by what was opened: a FIFO put in their place can no longer hold the daemon, or a helper. - A task is stale as its gate judges it: the watch ended, records no task where the task has an id, or records another one, a task with no id included. - The PR watches page reads a locked workspace by its own route, finds a watch's task in any workspace it lists, lists a clone's watch once, and sends a watch no gate checks to a session here rather than a terminal. - The rail offers the page only when the daemon lists watches, and the view goes back to the chat when it stops doing so. - Docs follow. * test(web-shell): drive the PR watches page and a watch's task card in Chromium Against the mock daemon: the sidebar opens the page, each watch shows where it is, a watch run elsewhere or by nothing says where to act, and Stop watch sends /autofix-pr stop <n> to the session that owns it. The scheduled-task card of the watch shows the same state. * fix(web-shell): restore a watch's session in its own workspace, and fence the listing - Open session and Stop watch, and a scheduled task's run and history, restore the session in the workspace of the task bound to it: from another workspace's conversation the daemon refused the restore. The PR watches page finds that task in whichever workspace holds it. - The watch listing checks its runtime generation before and after it asks git: a workspace removed or replaced meanwhile lists nothing and answers 503 workspace_runtime_unavailable, on both routes. A failure is logged by the daemon. - A task left behind by a take-over is shown stale on its card, as its gate stops it. - The integration test's baseline capability list names autofix_pr_watches. * fix(web-shell): name the one workspace of a single-project daemon for a watch's session With one ordinary workspace the PR watches page and the scheduled-tasks page recorded no workspace for a task, so Open session, Stop watch and a task's Run now and history, started from a standalone or Live conversation, restored the session in that context rather than the workspace. Both pages now name the primary workspace when it is the only one; its route stays the unqualified one. * test(web-shell): capture the PR watches page and a watch's task card in the visual preview No visual scenario answered GET /autofix-pr/watches, so the before/after preview could not see either surface. The scenario serves three watches (fixing in the background with an item for a person, run from a terminal, merged with nothing running it) and the watch's scheduled task, against the fixed capture clock. |
||
|
|
eea44208b8
|
ci: run every suite but CLI and core in the side test lane (#15570)
* ci: run every suite but CLI and core in the side test lane
The Linux test job's main lane ran the CLI suite and then, one after
another, core, about twenty smaller workspace suites and scripts/tests,
while the side lane (web-shell, then the standalone regression) went idle
long before the CLI suite finished. The main lane now runs only the CLI
and core suites; the side lane runs every other workspace's test:ci, the
standalone regression and scripts/tests, each at half the Vitest worker
cap. Every side-lane suite runs even after another fails.
* ci: print the side lane of a test step killed before its replay
The lane script replays the side lane's log only after the main lane
ends, so a step timeout or cancel lost every suite but the CLI and core
ones. The script now writes that log to SIDE_LANE_LOG when the caller
names one; ci.yml names it under runner.temp, outside the TMPDIR the step
removes, and a failure()/cancelled() step prints whatever is left of it.
The lane test also checks that the main lane's output is present before
it checks its order against the replay, and a comment that still placed
scripts/tests in the main lane now names the side lane.
* ci: keep the lane script's log out of a nested run
scripts/tests runs `npm run test:scripts:unit`, which runs the lane script
again, inside the step that names SIDE_LANE_LOG. The nested run inherited
that name and truncated, replayed and removed the outer side lane's log, so
the outer replay had nothing to print. The script now unsets the variable
once it has read it, and the lane test drives a nested run with a named log
to pin that: the nested run must not see the caller's log, and the outer
side lane's own output must still reach its replay.
The print-step test also pins the `if [ -f "${side_log}" ]` guard around the
cat, which is what keeps that step quiet on an ordinary red test job.
|
||
|
|
7b69fccd74
|
feat(tools): let an explicitly configured search backend name its host (#15638)
`classifyDashScopeBaseUrl` accepted a fixed list of DashScope-family suffixes, so an organization that routes model traffic through its own gateway had no configuration-only way to point the built-in `web_search` backend at that gateway: the tool stayed off with an `unknown-host` notice, and the alternatives were patching the source or dropping the built-in tool for an MCP search provider (#15299). Add `tools.webSearch.allowedHosts` / `WEB_SEARCH_ALLOWED_HOSTS`: extra suffixes matched exactly like the built-in ones (exact host, or any subdomain of a listed suffix). Empty by default, so behaviour is unchanged for anyone who does not set it, and the `https:` requirement still runs first because the side request carries a bearer key. The list reaches only the two places where the user named the backend: the `WEB_SEARCH_BASE_URL` path and the `modelProviders` path. Automatic derivation keeps the built-in list in both of its host tests — the entry usability check and the auto-eligible host check — so a host nobody declared is never adopted implicitly and never receives the main provider's key. Entries are trimmed, lower-cased, de-duplicated, and entries without a dot are ignored, so a typo narrows the list instead of widening it (a bare `com` would otherwise accept every `.com` host). Both rejection notices now name the setting, which is what a user hitting the gate needs. |
||
|
|
98312b31cc
|
feat(daemon): add cached workspace artifact queries and batch removal (#15594)
* feat(daemon): add cached workspace artifact queries and batch removal * fix(daemon): Share artifact cache with single-session queries * feat(daemon): Add artifact library session metadata and streaming --------- Co-authored-by: 丁炳智 <dingbingzhi.dbz@alibaba-inc.com> |
||
|
|
ad8a542013
|
feat(web-shell): manage extra workspace directories from settings (#15393)
* feat(web-shell): manage extra workspace directories from settings
The CLI's `/directory add` extends a session's workspace context with extra
directories, persists them at workspace scope, and reloads memory. Web Shell
had no equivalent: the settings panel never rendered
`context.includeDirectories`, and no daemon route exposed it.
Add a workspace-scoped REST surface backed by a new ACP control method. The
handler validates the path, applies the delta to the workspace config and every
live session, persists `context.includeDirectories`, refreshes hierarchical
memory when configured, and notifies the model. The Web Shell exposes the list
under Settings -> Workspace and reuses the existing directory dialog for path
input, autocomplete, and the native picker.
The route is trust-gated. SSH, managed, standalone, and experimental
paired-engine daemons do not advertise or serve it. The primary directory
cannot be removed; a session that cd'd into an extra directory is skipped with
a warning, and a directory that disappeared from disk can still be removed from
the list.
* fix(ci): align capability baselines and keep settings keys out of the transcript bundle
`workspace_directories` is a new registered and conditional capability, so the
ordered baseline lists in the unit and integration suites need it at the
registry position, and the conditional drift test needs a predicate branch.
The Web Shell category label no longer uses a `settings.category.*` i18n key.
The transcript build shares the i18n dictionary and deliberately carries no
`settings.` literals; the section title already exists as a non-settings key.
* fix(cli): keep workspace directory edits consistent with persisted settings
Three review findings on the workspace-directory mutation:
- Re-read the workspace settings layer before deriving the next list. The ACP
child's LoadedSettings snapshot can lag a write made by another owner, so an
add or remove could overwrite or miss entries already saved on disk.
- Persist with `throwOnWriteFailure` and before touching live roots. A refused
write now fails the mutation instead of reporting `changed: true` against an
unchanged file.
- Resolve entries the way runtime configuration does (`~`, relative paths
against the workspace root, then realpath) for display and matching, while
persisting the original spelling. A relative entry such as `../first` is now
removable from the UI, and removal still matches the persisted entry.
* fix(cli): resolve workspace directory entries like runtime settings
The second review round found two remaining P2s and one test-contract gap:
- Read, match, and remove entries from the settings layer with environment
variables resolved (`workspace.settings`), so a `$VAR` entry is listed as the
path sessions use and removal clears that path from live contexts. Persist
the original spelling from `originalSettings`.
- Refuse adding the workspace's primary directory (`primary_directory`); it is
always part of the workspace and previously persisted as an extra entry that
DELETE then refused as `initial_directory`.
- Accept relative and `~` removal paths in the handler, not only in the route,
and test the downstream handler with the relative spelling. The route test's
bridge mock had masked that the handler still required an absolute path.
* test(cli): canonicalize temp roots in workspace directory tests
The route and handler fixtures composed expected paths from the raw
`os.tmpdir()` spelling. On macOS that is `/var/...` while the implementation
resolves entries through `realpath`, which returns `/private/var/...`, so the
three new cells failed only on macOS.
Realpath the temp root once at setup in both files, so fixture paths and the
implementation's resolved output share the same spelling. No production code
changes.
* fix(cli): list workspace directories through the selected runtime environment
A directory variable defined only in the workspace's `.env` reaches the ACP
child and its settings but not the daemon process. The GET route read settings
with `skipLoadEnvironment`, so it interpolated against the daemon's environment
and listed the unresolved placeholder while live sessions used the real
directory; deleting that displayed path answered 200 with `changed: false`.
Read the workspace scope through a settings snapshot built from the selected
runtime's environment (and its trust), matching what the mutation handler and
its sessions resolve. Cover a variable that exists only in the runtime overlay.
* fix(cli): drop the daemon-environment fallback from the directories list
The process.env guard in `packages/cli/src/serve/**` only permits registered
accesses. Reading `process.env` as the parent-process fallback for the
directories list tripped it and failed the Test lane.
Only a runtime overlay carries a workspace `.env`, so the overlay environment
is the whole source: a runtime without one resolves the persisted list against
an empty environment, which keeps placeholders rather than silently borrowing
the daemon's process-wide variables.
* fix(web-shell): close the four Critical review findings
- SSH detection used a `ssh://` cwd prefix, but registration rewrites an SSH
workspace to a local mirror directory before it is served, so the gate never
fired and the section rendered against a boundary that answers 501. The route
now tests `routeFileSystemFactory.sshWorkspace` (as `capabilities.ts` does)
and the section tests the workspace entry's `ssh` marker.
- The route mapped every unclassified bridge failure to a flat 500. The first
mutation on a freshly opened workspace spawns a control channel, and that
spawn times out or is refused while the child starts; those carry none of the
feature's error kinds. The shared mapper is now the fallback, so a retryable
state keeps its 503/504 + Retry-After semantics.
- The section did not serialize mutations: Add was gated only on the initial
load, so an add could race a pending remove over the same settings
read-modify-write and silently drop one change. One gate now covers both
directions, with a ref so it holds before the next render.
- Browse rendered unconditionally, dropping the `native_directory_picker` gate
the same dialog gets in App; a headless daemon serves this section but answers
the picker with 501. Browse now follows the capability.
Tests pin each: the SSH route case uses the production shape (local mirror cwd +
runtime marker), the section case uses the capability marker, the fallback case
runs the real shared mapper, and a deferred remove asserts Add is disabled.
* fix(ci): type the shared-mapper mock and split the home-root trust gate
The prior commit typed the test harness's `sendBridgeError` option as a loose
`(res: unknown, ...)` function, which `tsc --build` rejects when it is handed
the real `SendBridgeError`: the whole `prepare` build failed, so every CI job
that installs dependencies went red. Type the option as `SendBridgeError`
instead.
Also separate two states the handler conflated: `workspaceSettingsActive` is
false both for an untrusted workspace and for a trusted workspace rooted at
`$HOME`, and only the first is a trust failure. Trust is now read from
`config.isTrustedFolder()`; a home-rooted workspace answers
`directory_management_unsupported` rather than an `untrusted_workspace` code no
grant can clear. `makeConfig` gains the `isTrustedFolder` the handler now calls.
The section seeds the dialog with the workspace root so browse mode lists
folders on open, and its placeholder no longer advertises an `ssh://` spelling
this route rejects.
* fix(web-shell): gate the settings category on the SSH marker in App
The Settings root still decided whether to supply the Workspace directories
section from `workspaceCwd.startsWith('ssh://')`. Registration rewrites an SSH
workspace to a local mirror directory, so that test never fires: for a
registered SSH workspace App passed a truthy element, SettingsMessage created
the category, and the child section then rendered nothing — a visible, selected
category with empty content and no list request.
Decide it from the selected workspace entry's `ssh` marker, the same signal the
section uses, so App passes `undefined` and the category is omitted.
* fix(cli): pair workspace directory entries by tail, not by index
`workspaceFile.settings` is the merged layer, and `context.includeDirectories`
merges with `MergeStrategy.CONCAT`, so a user-scope value lands ahead of the
workspace entries. The handler paired that merged array with the
workspace-scope array by index, so once a user scope also set the key, a
workspace entry resolved to an unrelated user path: the panel showed the wrong
directory and removal left the real root live in every session.
The workspace entries sit at the END of the merged array, so take that tail
instead. Each workspace entry then resolves to itself, and a `${VAR}` entry
still matches the path sessions use.
The regression case seeds a user-scope prefix ahead of one workspace entry and
asserts the removal clears it from both live contexts; index pairing fails it.
|
||
|
|
3eff5f7793
|
feat(web-shell): create workspaces from a Git URL (#15361)
* feat(web-shell): create workspaces from a Git URL
The Add-workspace dialog could only register an existing directory or
create an empty scratch directory, so starting from a repository meant
leaving the browser, cloning on the daemon host, and registering the
path afterwards.
Add a Git source that starts an asynchronous clone job, reports progress,
and supports cancellation. A successful clone is registered through the
existing workspace route, so capacity, nesting, persistence, and the
daemon's trust policy stay in one place.
* fix(cli): classify git_workspace_clone in the capability parity matrix
The parity matrix landed on main after this branch, and every capability
tag needs a row. Managed workspace creation only creates empty
directories, so record the clone surface as an explicit Managed gap
rather than claiming parity.
* fix(cli): reserve active clone targets before overlapping work
An outer clone's failure/cancel cleanup removes its target recursively,
so a nested clone or workspace registration must not start underneath a
running job. Reserve the canonical target for the job lifetime, reject
overlapping clone and registration paths, and release the reservation
only after cleanup settles.
Also resolve the success path before publishing `succeeded`, so a reader
never observes a terminal snapshot without the `cwd` it registers.
* fix(web-shell): keep clone polling recoverable
A failed poll left the last known status running, so the panel offered
Cancel instead of the recheck that can recover the existing job. Prefer
the unknown-state action.
Reset the unmounted guard in the effect setup: StrictMode's development
replay otherwise leaves it set and discards every later poll, including a
successful clone.
* feat(web-shell): reuse the directory path as the clone parent
When the source switches to Git, treat the directory path as the clone
parent and append the repository name from the URL. A path whose basename
already matches stays as-is, and a manually edited or browsed target is
never overwritten by later URL edits.
* feat(web-shell): suggest folders for the clone target
Headless daemons do not provide a native directory picker, so the clone
target had to be typed in full. Reuse the directory suggestion list:
selecting an existing folder uses it as the parent and appends the
repository name, while a manually edited target still wins.
* fix(cli): keep clone progress monotonic
git restarts the percentage for some clone phases (remote counting,
then receiving), so the Web Shell progress bar could jump backwards.
Clamp the reported percent to the running maximum; the status message
still follows the current git line.
Also pin the clone admission guards left unpinned by review: reserved
roots, unknown request keys, promotion of a workspace under an active
clone target, file URLs that name a host, percentages above 100, and
the dialog fallback when the daemon capability disappears.
* fix(cli): reserve the clone target before claiming it
The claim created the target with mkdir, then awaited the existence check
and the final realpath, and only reserved the canonical path afterwards.
A concurrent ordinary registration could admit the freshly created
directory in that window and then lose it to the clone's cancel/failure
cleanup.
Reserve the canonical target inside claimCloneTarget before mkdir, share
the reservation with registration, and release it on every claim failure
and on post-claim admission failures. Add a regression test that overlaps
the registration with the blocked claim and a unit test that asserts the
reservation exists before the directory does.
Also update the serve integration capability envelope for the
host-conditional git_workspace_clone tag, mirroring the daemon's probe.
* fix(cli): register git_workspace_clone in serve capability guards
Test (ubuntu) failed five capability-registry guards because the new
git_workspace_clone tag was not registered alongside the existing
conditional features:
- document it in the qwen serve protocol table;
- allow the clone spawner's process-environment copy and SystemRoot
lookup in the serve process.env guard;
- add it to EXPECTED_REGISTERED_FEATURES and the conditional-feature
drift-insurance branch.
Verified: capabilities-docs-contract, process-env-guard and server
suite 1399/1399; typecheck, ESLint, Prettier clean.
* fix(cli): keep clone targets away from reserved and Live paths
A clone target that contains the reserved Conversations root was accepted
when that root did not exist yet: creating the target made the root's
parent part of the clone, and the Live runtime could then be published
inside it. Because the Live exemption skipped the "canonical inside
target" direction for active clone targets, cancelling or failing the
clone deleted the registered Conversations workspace.
Reject clone targets that contain a reserved root, even before the root
exists, and apply strict two-way nesting to active clone targets in the
owned-runtime admission regardless of provenance.
Verified: workspace-git-clone + workspace-management 196/196;
typecheck, ESLint, Prettier clean.
* fix(cli): resolve reserved roots through existing ancestors
The reserved-root containment check fell back to the unresolved spelling
when the Conversations root did not exist yet. A symlinked ancestor such
as ~/Documents then made the canonical clone target and the lexical
reserved path disagree: the clone was accepted, the Live runtime created
the root inside it, and cancelling the clone left ConversationWorkspace
with a cached identity for a directory that no longer existed.
Resolve each path through its nearest existing ancestor and append the
missing suffix before comparing, so a clone target containing a missing
reserved root is rejected before mkdir even when ancestors are symlinks.
Verified: workspace-git-clone + workspace-management 197/197;
typecheck, ESLint, Prettier clean.
* fix(cli): cover case aliases and dangling links in reserved roots
The reserved-root guard compared the reconstructed missing suffix with
case-sensitive isWithinRoot, so a case-variant ancestor such as
`Documents/qwen code` did not match `Documents/Qwen Code/Conversations`
on a case-insensitive filesystem. It also walked past a dangling symlink
ancestor, so a clone target that was the link's missing destination
could later make the link valid inside the clone's cleanup scope.
Compare canonical paths case-insensitively after resolving through the
nearest existing ancestor, and follow dangling symlinks while
reconstructing the missing suffix. Add regressions for both aliases.
Verified: workspace-git-clone + workspace-management 199/199;
typecheck, ESLint, Prettier clean.
* fix(cli): harden git clone credentials, config, and admission
- Strip URL credentials before git's argv and supply them through a
command-line credential helper backed by the child environment, so the
secret no longer appears in /proc/<pid>/cmdline or .git/config.
- Disable global/system git config for the clone child so a repository's
.gitattributes cannot run host-configured filter commands during checkout.
- Keep the first fatal/error stderr line for clone failures instead of the
trailing SSH boilerplate.
- Only treat Receiving objects/Resolving deltas as progress phases, so
remote counting cannot pin the bar at 100.
- Strip OSC/C1 and all other control characters from clone messages; add
the missing Windows taskkill 'error' listener.
- Require operator authority for clone cancellation; reject clone targets
inside the managed scratch root or equal to a registered/in-flight
workspace; keep case-insensitive containment scoped to clone targets.
Verified: workspace-git-clone + workspace-management 205/205;
typecheck, ESLint, Prettier clean.
* fix(web-shell): gate the clone flow during submit and IME input
- Disable the post-clone Back button while registration is in flight and
guard backToForm, so it cannot reset a submitting flag owned by onAdd.
- Ignore clone-target key handling while an IME composition is active.
- Dismiss the clone-target suggestion list when the git form is submitted,
so its Escape interceptor cannot stay armed over the progress view.
Verified: AddWorkspaceDialog 57/57; Prettier and ESLint clean.
* docs: align git clone guarantees with credential and config isolation
- Document the `{ kind: "git" }` form and its 202 job response in the
`POST /workspaces` reference.
- State that URL credentials are moved out of git argv and the stored remote
URL, and that global/system git config is disabled for the clone child so
committed filters cannot run host commands during checkout.
- Mirror both changes in the Chinese design document.
* fix(cli): use bracket access for clone credential env
The strict index signature on NodeJS.ProcessEnv rejects dot access to
QWEN_GIT_USERNAME/QWEN_GIT_PASSWORD; all CI jobs that build the CLI
failed at tsc with TS4111 before running any test.
* fix(web-shell): cancel the running clone from the left action
- Route the progress panel's left Cancel through cancelClone while the job
is running, so it stops the daemon job instead of only closing the dialog.
- Clear the pending poll timer at pollCloneJob entry and before cancel, and
guard registration per job id, so a terminal snapshot cannot register the
same clone twice.
Verified: AddWorkspaceDialog 57/57; Prettier and ESLint clean.
* fix(web-shell): harden git clone env and clone-job dialog states
Pin LC_ALL/LANG for the clone child and sweep host Git config channels (GIT_CONFIG_*, GIT_ASKPASS, GIT_SSH*, GIT_PROXY_COMMAND) so a localized git still reports parseable progress and a browser-chosen URL cannot borrow host credentials. Decode URL credentials defensively, reject control characters, and print the inline credential helper with printf so dash does not mangle tokens.
In the add-workspace dialog, tear the clone state down when the capability disappears, report a pruned clone job instead of offering recheck forever, honour a cancel that loses the race with a finished clone, and close instead of cancelling an unconfirmed job. Correct the host-authentication prose in the design docs, README and URL hint to match what the isolated clone child actually supports.
* fix(cli): resolve relative symlinks physically and keep cancel intent
- Resolve a relative symlink target from its physical parent before appending
the missing suffix, so a dangling relative Documents link cannot hide the
reserved Conversations root from clone admission.
- Keep the per-job cancellation intent across overlapping poll responses, so
a second succeeded poll cannot auto-register after Cancel. Clear it only on
an explicit Back/new submission.
Verified: route + clone 209/209; AddWorkspaceDialog 61/61; typecheck with a
6 GB heap; Prettier and ESLint clean.
---------
Co-authored-by: verify <verify@local>
|
||
|
|
1df5031e70
|
perf(ci): overlap web-shell and standalone tests with the CLI suite (#15438)
* perf(ci): overlap web-shell and standalone tests with the CLI suite The Linux Test job ran every workspace suite, the standalone runtime regression and scripts/tests one after another. The CLI suite keeps its workers busy for 13-20 minutes, while web-shell's suite spends most of its run on App.test.tsx alone and the standalone regression starts one runtime process at a time, so those two added their mostly idle minutes straight to the job. Run them in a side lane beside the other workspaces instead. .github/scripts/ci/run-unit-test-lanes.sh starts web-shell and the standalone regression in the background, runs every other workspace's test:ci (listed with npm pkg get, in npm's workspace order) and then scripts/tests, and replays the side lane's log after the main lane so the two never interleave. The step runs the script under the same setpriv wrapper, so every test command still runs without the DAC override capabilities. test:scripts now reaches its Vitest run through a new test:scripts:unit script with a trailing --, so the main lane can run it without the standalone regression while test:ci, test:ci:workspaces and test:scripts run what they ran before. * fix(ci): name the cause when the lane script cannot start a lane - Fail with an ::error:: line when mktemp cannot create the side lane's log file, instead of running the main lane alone and dying in tail. - Check the exit status of `npm pkg get` and of the parser. A failing `npm pkg get --json` prints an error object, which used to parse as an empty listing and report "Found no workspaces". - Note in the header that the replay puts side-lane failures after the main lane's in main-failure-signature.mjs's title and search markers. - Lane test: clear the caller's NODE_OPTIONS, assert the main lane's output is present before checking its order, and cover the failed listing and the missing temp dir. - Cross-check npm's workspace resolution against the selector in scripts/workspaces.js. * fix(ci): halve web-shell's worker cap beside the main lane Each Vitest run applies VITEST_MAX_FORKS / VITEST_MAX_THREADS on its own, so running two lanes at once doubled the step's worker cap, and on the hosted runners, where no cap is set, put two all-CPUs pools on 4 vCPU. The lane script now runs web-shell at half the cap, at least 1, and at 2 where the cap is unset; the main lane keeps the full cap. - Refuse a workspace name with whitespace in it: the main lane's arguments are read one per line, so a line break would split a name into other arguments, flags among them. - Lane test: web-shell and the main lane's suites now wait for each other, so a side lane serialized before or after the main lane fails; record each call's worker caps; run the script against npm's real listing and compare its main lane with the selector the release pins use; cover a name with a line break in it. - ci.yml: the standalone runtime cache comment names the lane script's side lane, not test:scripts, as what runs the regression in this job. - ci-variables.md: say the cap applies per Vitest run and what the two-lane step runs at its peak. - release-workflow.test.js: follow test:scripts through test:scripts:unit to vitest; #15102 pinned the shape before the split. |
||
|
|
92459f93dc
|
feat(serve): persist max-sessions in settings.json (#15419)
* feat(serve): persist max-sessions in settings.json `qwen serve --max-sessions` could only come from the command line, so a restart without a launch script silently fell back to 32. Add `serve.maxSessions` to settings.json and expose it in the Web Shell settings panel. Resolution is `--max-sessions` > `serve.maxSessions` > 32. The key is operator-owned: a workspace settings file is stripped from the merge and named on stderr at boot, and the serve fast path skips it structurally. An invalid value is reported and treated as absent so a typo cannot take the daemon down or silently disable backpressure; 0 disables the cap. Dropping the yargs default is what keeps an omitted flag distinguishable from an explicit 32. The Web Shell allowlist carries the key because it is showInDialog: false. The panel writes it at user scope; the existing workspace-scope refusal still applies. Design: docs/design/2026-10-24-serve-max-sessions-setting.md Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): keep operator settings when workspace settings are malformed A workspace `.qwen/settings.json` containing `[]` (or invalid JSON) made `loadServeFastPathSettings()` throw before it returned the already-read user/system/system-defaults scopes. The serve boot catch then left `bootSettings` undefined, so `serve.maxSessions: 3` was lost and the bridge fell back to 32 — a repo-local file could effectively raise an operator-owned cap. Isolate the workspace read in the serve boot: `onWorkspaceSettingsError` skips only that scope and reports it, while the default fast-path contract still throws so the full settings loader can take over. Regression covers both `[]` and malformed JSON with user `serve.maxSessions` / `serve.tokenQr` set. Real daemon: user 3 + workspace `[]` now reports 3 (was 32); the four precedence/fallback scenarios still report 3/5/32/32. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(serve): pin the settings-sourced total cap and default Two non-blocking triage notes: - The schema default was pinned to a literal 32 while the adjacent sub-session test pins its default to the runtime constant. Assert it against advertisedMaxSessions(undefined) so a future DEFAULT_MAX_SESSIONS bump cannot leave the schema and vscode mirror advertising 32 silently. - A settings-sourced per-workspace cap feeds deriveDefaultMaxTotalSessions, but no test exercised that path. Boot two workspaces at maxRegisteredWorkspaces: 2 with serve.maxSessions: 3 and assert /capabilities reports maxTotalSessions: 6. Test-only. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
779a4d5d0d
|
feat(serve): carry a scheduled-task gate through the daemon and Web Shell (#15211)
Some checks are pending
E2E Tests / E2E Test - macOS - shard 1/2 (push) Blocked by required conditions
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Lint & Static (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / web-shell E2E Smoke (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (no-AK, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Blocked by required conditions
E2E Tests / Build for E2E (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 2/2 (push) Blocked by required conditions
E2E Tests / E2E Interactive - OpenTUI renderer (bun) (push) Blocked by required conditions
E2E Tests / channel-plugin E2E (nightly) (push) Blocked by required conditions
E2E Tests / cron-interactive E2E (nightly) (push) Blocked by required conditions
E2E Tests / web-shell Browser Regression (push) Waiting to run
Managed Agent gate / Select the Managed Agent tier (push) Waiting to run
Managed Agent gate / SDK Java () (push) Blocked by required conditions
Managed Agent gate / Managed Agent gate (push) Blocked by required conditions
SDK Java Contract Open PRs / Re-check open PRs against main (push) Waiting to run
SDK Java Flyway Open PRs / Re-check open PRs against main (push) Waiting to run
Verify Landlock helper / Native Linux CLI boundary (push) Waiting to run
Verify Landlock helper / Native Windows settings and streams (push) Waiting to run
Verify Landlock helper / arm64 (push) Waiting to run
Verify Landlock helper / x64 (push) Waiting to run
* feat(serve): carry a scheduled-task gate through the daemon and Web Shell
A gated scheduled task (PR 1a) could only be created from the TUI, and the
TUI refused a gate on a `sessionMode: "current"` task because nothing on the
daemon side knew the field. This threads `gate` through every path a task can
take in the daemon and surfaces it in the Web Shell.
- `cron_create` accepts a gate on a current-session task; the request reaches
the daemon over the ACP bridge (`gate` must be a non-empty string within the
2048-char cap, else `invalidParams`) and the daemon wraps the task with
`withTaskGate`, so the downgrade fuse (`condition`) stays in step.
- REST: POST accepts `gate` (400 `invalid_gate`, 400 `gate_requires_recurring`
on a one-shot), PATCH sets, replaces or clears it (`null`/`''`) and resets
the gate status when the command changed; a gate edit on a legacy guarded
task is refused with 409 because its `condition` is its own fail-closed
marker, not a fuse. Task views carry `gate` and `gateStatus`.
- A manual run of a gated task has no scheduler in the loop, so the run prompt
asks the model to run the gate itself first, under the usual tool approval,
and stop when it prints nothing (`buildGatedManualRunPrompt`); per-run
manual runs and the Web Shell's persistent-task "run now" both use it.
- Capability `scheduled_task_gate` (`{ since: 'v1' }`) advertises the field;
the Web Shell shows the gate editor, card line and status only when the
daemon lists it.
- `gateCommandError` moves the shape check into core so the tool, the bridge
and the routes agree on what a gate may be. Its `&` check is
`backgroundsAnyPart`, so the routes refuse a backgrounded wrapper script
(`nohup bash -c './probe.sh &'`) exactly as `cron_create` does.
Tests cover the tool, the bridge, the ACP session, the routes (POST/PATCH/run
and the existing-session creator), the run prompt and the dialog. Docs: the
protocol capability table, the user guide's daemon section and the design
doc's status.
* test(serve): list scheduled_task_gate in the integration capability envelope
* fix(core): keep a current-session gate confirmation and reset a replaced gate's streak
A gate confirmed through the dialog for a `sessionMode: "current"` task was
blocked at its first fire: the daemon writes the task file and this
session's scheduler installs the job on its next reload, with nothing to
inherit the confirmation from. `cron_create` now hands it to the scheduler
(`approveGate`), which marks the job when it is loaded or when the reload
installs it, and only while the task's gate is that command.
A reload also kept the in-memory gate status when the file had none, even
after the gate command changed, so a replaced gate continued the old
failure streak and its first failure was counted without a notice. The
in-memory status now carries over only for an unchanged gate.
* refactor(serve): single-source the gate cap in the bridge and drop barrel churn
* fix(core): keep a replaced gate's late result off the new command's status
The gate status write matched the task by id only. When a PATCH replaced
the gate and dropped its status, the old command could still settle before
this session's watcher reloaded the task, and its write put the old
command's streak back onto the new gate: the new command's first failure
was then counted without a notice. The status is now written only while
the task's gate on disk is the command that produced it; the run record of
that fire is still appended.
* fix(core): keep the tick's gate status write on its own command
The tick persists a gated fire's stamp together with the job's in-memory
gate status, matched to the task by id only. When a PATCH replaced the
gate and dropped its status, a tick landing before this session's watcher
reload wrote the old command's streak back onto the new gate, so the new
command's first failure was counted without a notice. The tick now keeps
the command with the status and applies it only while the task's gate on
disk is still that command.
* fix(serve): send the gate only when edited and tighten what a gate may be
Web Shell:
- An edit sends `gate` only when the field changed it. The route reads any
`gate` key as a gate edit, which a legacy guarded task refuses (409), so an
untouched field made prompt and cron edits fail on those tasks.
- A one-shot edit shows no gate field and sends no gate (the daemon refuses
a gate there and would discard the whole save).
- The field is a textarea, so a stored multi-line gate is not flattened on
the next save.
- The card's status line leads with the outcome and reason while the gate
fails or is blocked, and the hint states what the runner really does: exit
0 with output delivers, empty stdout skips, a non-zero exit reports stderr,
and unattended runs need a rule, a read-only command or auto/yolo.
Core: `gateCommandError` refuses a command of only comments (it runs as
nothing, so every fire would be quiet) and reads a trailing `\&` as a
literal while `\\&` still backgrounds.
Docs: `scheduled_task_gate` is unconditional, so it leaves the conditional
capability table (the docs contract test counted it there) for its own
paragraph with the recurring and legacy refusals, the `blocked` outcome and
its preconditions, and the per-method behaviour of an older daemon.
Tests cover each change, plus the shared validator's length branch, a gate
on a create bound to a caller session, and `approveGate` refusing a loaded
job whose command differs.
* fix(core): read a gate's comment scope from its shell structure
The comment-only check ran on the permission text, which joins a
wrapper's script with whatever follows the wrapper: `bash -c '# x' &&
printf 'ready\n'` read as one comment line and was refused, though the
command after the wrapper runs and prints. `runsOnlyComments` now checks a
plain command line by line and a wrapper by its own script, recursively,
counting anything after the script as a command.
* fix(serve): close audit gaps in gate validation, confirmation and edits
- A daemon that predates gated tasks drops a requested gate on a
current-session create. The daemon now echoes the stored gate through
the bridge to the child, and `cron_create` removes a task that came back
without its gate and refuses, instead of leaving a task that runs at
every slot.
- `stop()` keeps pending gate confirmations: a turn cancelled before the
reload that installs the task stops and re-enables the scheduler.
- PATCH refuses a gated one-shot only when the edit makes it; a task
already in that state keeps its other edits and can clear the gate.
- `backgroundsAnyPart` ignores comments and counts the backslashes before a
trailing `&` at every level, wrapper scripts included: an `&` in a
comment no longer refuses a gate, a quote in a comment no longer hides a
real `&`, and `bash -c 'echo \\&'` is refused like its top-level form.
- Docs: PATCH clears a gate with null or "", the one-shot refusal is per
edit, the `&` check is lexical, and binaries up to v0.19.8 ignore the
downgrade fuse.
* fix: read gate scripts with and without comments and close audit edge cases
- Read every level of a gate twice, with comments stripped and raw, and
refuse an `&` either reading finds: a `#` misread inside `$(...)`,
`$'...'` or an escaped wrapper quote can no longer hide a real `&`.
The check errs toward refusing, documented in the design, protocol and
user docs.
- Treat plain words after a wrapper script as its positional parameters
when checking for a comment-only gate.
- Name the ungated task left behind when removing it after a daemon
dropped the gate fails.
- Re-sending an unchanged gate or recurring flag no longer counts as
making a new one-shot gate.
- Web Shell: allow clearing the gate of a one-shot gated task, refuse
to submit a typed gate once the daemon stops advertising gates, drop
the client-side length cap, and fix the gate field comment.
* fix(core): fail a quiet gate whose background part still holds its output
The lexical `&` check cannot follow every spelling: a quote it misreads
inside "$(...)" or $'...', a script handed to eval, piped into a shell,
passed as `bash -c -- ...` or put together from several quoted pieces
all get past it, and such a gate settles quiet at every fire.
Back it with the runner: the shell service now marks a streamStdout
result whose output pipes were still open when the post-exit drain ran
out (`outputHeldAfterExit`), and a gate that exits 0 with nothing on
stdout in that state is reported as failed instead of quiet. A gate
that printed in the foreground is still delivered.
The docs now call the lexical check best-effort and describe the
backstop. Also moves approveGate's doc comment back onto it.
* fix(core): judge a gate's backstop on stdout alone and stop what it left behind
- The shell service now reads the stdout end on streamStdout runs, and
reports `stdoutHeldAfterExit` only when stdout itself was still open as
the post-exit drain ran out. A helper that silenced its stdout but kept
stderr no longer turns every quiet fire into a failure.
- When a gate's stdout is still held, the runner kills the gate's process
group: the gate is over by contract, and each fire would leave one more
process behind.
- Quiet is judged on the stdout the session would see: output of escape
sequences alone (`tput sgr0`) no longer wakes the session with an empty
gate-output block.
- The manual-run instructions tell the model that a command which puts
part of itself in the background counts as a failure, matching the
scheduled fire.
- Docs: the backstop's stdout-only rule, the stopped process group, and
the bwrap shell execution sandbox, whose PID namespace ends a background
part with the command so only the lexical check applies there.
* fix(core): stop a gate's leftovers gently, keep cut and escaped output, align manual runs
- Leftovers: the gate's process group gets SIGTERM first, so a part that
holds a lock can release it, then SIGKILL 200 ms later. Docs and the
comment now say what is out of reach: a part in its own process group
or session, and Windows.
- Quiet: a cut output is never quiet, since what was kept may be noise in
front of what was cut. Sanitizing strips escape sequences line by line,
so a title sequence left open cannot swallow the lines after it.
- Durations run on the monotonic clock.
- Manual runs: a gate that printed nothing because it put part of itself
in the background is the failure; one that printed is followed, as the
scheduled fire delivers it.
- Tests: the leftover-stop test keeps process.kill stubbed and its timer
faked for the whole case, so no run signals a real process group.
* fix(core): judge manual gate runs on stdout, defang open structural tags, kill leftovers at exit
- Manual runs: the model runs the gate as `{ <gate>\n} 2>/dev/null`,
since a scheduled fire reads only stdout; a warning on stderr no longer
makes a manual run follow the task where the scheduled fire is quiet.
- Defang: a structural tag name is defanged however the tag goes on,
`</gate-output >`, a tab, attributes or the line's end, all of which
can still close the data block in XML.
- Leftovers: a process that exits before the SIGKILL is due sends it on
its way out, so a part that ignored SIGTERM cannot outlive a headless
run that ends with its last gate.
- A held stdout with nothing visible fails even when the output was cut.
- The leftover test restores process.kill and real timers in `finally`.
* fix(core): refuse a gate that carries a NUL character
A process argument cannot carry a NUL, so such a gate passed every check
and then failed to start at every fire. gateCommandError now refuses it,
which answers 400 invalid_gate on the routes and refuses it in
cron_create.
* fix: send a manual gate run as written, and defang only what parses as a tag
- Manual runs send the gate exactly as written again: the `{ … } 2>/dev/null`
wrapper kept the session's permission rules from matching it (allow
rules asked again, deny rules no longer applied, the read-only path
refused it) and broke under cmd.exe and PowerShell. The prompt instead
says only standard output counts and warnings on standard error do not.
- The structural tag defang matches a name followed by `>`, `/>`, an
attribute or the end of the line, so `</gate-output >` is still defanged
while `Promise<Result | null>` and `a <result && b` in monitored text no
longer gain an invisible character.
- A held stdout whose cut output showed nothing says so, instead of
claiming nothing was printed.
* fix(core): defang the gate's own wrappers in every tag form, keep Monitor's defang as it was
The gate block is not XML-escaped, so its defang is the whole defence, and
the shared Monitor regex only matched `<name>`: `</gate-output x>`,
`</gate-output/ >` or a name split by an invisible character still closed
the block. Widening the shared regex either missed forms or defanged
ordinary Monitor text (`Promise<Result>`), so monitor.ts is back to its
base, and the gate's sanitizer strips the invisible characters that could
hide a wrapper and defangs `gate-output` / `gate-stderr` whatever follows
the name.
Docs: a manual run applies `Bash(...)` rules and the read-only check to
the command as written; the scheduled fire checks it as a `Monitor`
command, and a `Monitor(...)` rule does not cover the shell call.
* fix(core): write every < in gate output as < instead of chasing tag spellings
Defanging the wrappers' names kept missing forms: a joiner or bidi mark in
the name, attributes, a tag spelled in tag characters, and a strip list of
invisible characters both leaked and damaged ordinary text (Persian ZWNJ).
The gate block is data the model reads, so every `<` in it is now written
`<` and its tag characters (U+E0000–E007F) are dropped: no tag can open
or close the block however it is spelled. The fire text and the failure
notice say so, and the docs describe it.
* fix(core): read a newline after a wrapper's script as a command separator
A gate such as `bash -c '# setup'` followed by a newline and a command was
refused as only a comment: the wrapper parse trimmed the newline away, so
the next command read as the script's positional words. The suffix keeps
its leading newline now (other readers trim it themselves).
* fix: let gate rules see a line after a wrapper script, and refuse any gate edit the daemon would drop
- Security: a gate's permission check read `bash -c 'echo hi'` followed by
a newline and `rm …` as `echo hi rm …` (the script and its positional
words), while the gate runs the text as written and the shell runs `rm`
as the next command; deny rules missed it and the read-only check
allowed it. The Monitor normalization now keeps that line on a line of
its own in the safety command (only stricter for Monitor, whose spawned
command is unchanged).
- A wrapper comment followed by a line of only comments is refused as
only a comment again.
- Web Shell: with gates no longer advertised, any change to a task's gate
(clearing one included) blocks the save with an error, instead of a
request without the `gate` key that kept the old command.
- Docs: the rule fix and the §4.2 example texts.
* fix(core): refuse a command after a shell wrapper's script in a gate
The permission rules read a wrapper's script and what follows it as one
command line, which is what Monitor spawns, while a gate runs the text as
written: a command after a newline, `;`, `&&` or `|` runs on its own.
Joining the next line with a newline in the safety command still let a
script ending in `\`, an open quote, a here-document start, or a blank
other than space before the newline hide that command from deny rules and
the read-only check. A gate now keeps every command inside the wrapper's
script; the Monitor normalization is back to its base.
Web Shell: a gate change made before the daemon stopped advertising gates
stays visible, so the change the save refuses can be undone.
* test(core): type the gate refusal table so the spread rows check
* fix(core): refuse a gate the Monitor normalization would rewrite
The permission rules check a gate as the Monitor normalization rebuilds
it: a shell wrapper's script unquoted and joined into one line, which is
what Monitor spawns. A gate runs as written, and the two kept differing in
new ways: adjacent quotes end the script early, a separator glued to it
hides the next command, a trailing `\`, an open quote or a command after
it does the same, and deny rules and the read-only check then judged a
command other than the one that ran.
Instead of chasing each spelling, a gate must now be a command the
normalization leaves exactly as written: a shell wrapper (`bash -c '…'`,
also behind env assignments) is refused. A gate already runs in a shell,
so the wrapper adds nothing. This replaces the narrower check on commands
after the script.
* fix(core): name the command the rules would check when refusing a gate, and drop stale wrapper notes
A plain command the Monitor normalization misreads as a wrapper
(`bash /opt/probe.sh -c x` reads as `x`) was refused as "wrapped in a
shell" with nothing to say why; the error now shows what the permission
rules would check. The design docs no longer list wrapper spellings that
are refused at creation as getting past the background check, the zh-CN
paragraph loses the leftover from the earlier rule, and the risks name
the PermissionManager's own blind spots that a gate inherits from the
shell tool.
* fix(core): keep the refused gate's checked command whole when cut, and word the refusal as a reading, not a mismatch
The command named in a wrapper refusal is cut at 80 units without
splitting a surrogate pair, and the error says the rules would check it
"not the command as written" rather than "instead of what runs", which
was untrue for a plain wrapper. The gateCommandError doc lists every
refusal again. In the design docs a gate gets the decision of a monitor
call (not of a shell command), and the acceptance criterion on older
binaries matches the fuse: v0.19.9 on never fire a gated task.
* fix(core): report a gate again when its failure streak changes kind
Blocked and failed fires share one streak, and only its first fire was
reported. A gate created over REST is blocked until its rule is added;
the blocked notice says further blocked fires are counted silently until
the gate runs again, yet when it then ran and failed nothing was said.
A notice now opens a streak and follows each change between blocked and
failed. The envelope comment and design docs also stop claiming more
than `<` escaping gives: look-alike brackets stay as text in the block.
* fix(core): report a gate blocked for another reason, and store the quiet fire that ends a failure streak
A gate blocked by a deny rule and then, once the rule was removed, only
unapproved was never told the rule to add: both fires were 'blocked', so
the second counted silently. A blocked fire now notifies when its reason
changes too.
A quiet fire's status only reached the file with the next tick persist,
so a restart in between reloaded the old failure streak and counted the
next failure into it without a notice. The quiet fire that ends a streak
is now written at once.
The tool descriptions, the Web Shell hint, the protocol and user docs and
the design docs say when a failure is reported in the same terms.
* fix(core): book a gated fire at the slot it ran for
The run record and the job handed to the consumer re-read the live job's
lastFiredAt when the gate settled; a manual run or a later slot stamped
while the gate ran moved it on, so the run landed under another fire's
timestamp and a per-run session's annotation matched the wrong entry.
Both now use the fire's own slot, which the status already did.
Also corrects the design docs on how often a gated task rewrites the
tasks file, and two scheduler comments the new notice and persistence
rules had made stale.
* fix: title a gated per-run session from its task, book runs in time order, call a gated slot checked
A gated fire hands the session the gate's delivered text as its prompt,
and a per-run fire titled its fresh session from that text instead of
the task prompt a manual run uses; the fire now carries the task prompt
too. A gated run is booked when its gate settles, under its own slot,
which can be older than a manual run booked meanwhile, so the run ring
now keeps time order. And the Web Shell card read a gated task's slot as
its last run, though a quiet gate ran no prompt: it now reads as checked.
* fix: refuse a gate holding an unpaired surrogate, and say "1 failure"
Bash receives the gate as UTF-8, which cannot carry an unpaired UTF-16
surrogate, so such a gate ran as U+FFFD rather than the text the
permission rules checked; it is now refused with the other malformed
gates, as main refuses it for a hosted shell command. The Web Shell gate
line also said "1 failures" on a gate's first failure.
* docs(core): name where a session drops a delivered gate output with its fire
* fix(core): never start a gate's stderr tail with half a character, and name where a session drops a gate output
The failure notice keeps the last 2048 UTF-16 units of stderr, a cut that
could leave the low half of a surrogate pair at its start; the notice is
a model prompt, and an unpaired surrogate breaks its serialization. The
cut now drops that half. The design docs describe the places a session
drops a fire, and a delivered gate output with it, as examples rather
than a closed list: a user prompt, a cancel or the token-limit breaker
clear queued fires too.
* fix(core): drop half a character at the stderr tail's start on a short tail too
The runner keeps a rolling stderr buffer cut in UTF-16 units, which can
also leave the low half of a pair at its start; the previous fix only
dropped that half when the tail itself was cut. It is now dropped either
way.
* docs: say the rule a blocked gate names may not cover a compound command
The rule is the one Monitor's "always allow" would save, which for a
compound command (a test of a command substitution, an if, a heredoc)
may leave a part unmatched, so the gate stays blocked; such a probe
belongs in a script.
|
||
|
|
7bda51a070
|
feat(core): add model.loopProtection and remind instead of stopping loops by default (#15016)
* feat(core): add model.loopProtection and remind before loop stops Add the model.loopProtection setting (stop, warn, off). Under the default stop, every stop point stays where it is; the always-on guards (identical calls, shell inspection, daemon invalid tool parameters, stuck repetition past the adaptive cap) first send the model one reminder with the next tool results. warn only reminds, except for content and thought repetition; off neither reminds nor stops. Budgets stop in every mode. A new loop_protection telemetry event records each reminded, stopped and observed decision. Loop stop hints and the TUI dialog note name the setting. Refs #14828 * feat(core): default model.loopProtection to warn A firing loop guard now reminds the model once and lets the turn continue by default. Budgets still stop every mode, and content and thought repetition still stops. Set model.loopProtection to "stop" to keep the previous stop points. Refs #14828 * fix(core): keep side queries out of the running turn's loop decisions A concurrent /btw side query shares the client's loop detector. Its retry dropped the turn's pending reminders, its stateful tool results could queue a global-duplicate reminder into the turn, and under `off` it marked patterns observed under the turn's prompt id. Route recordToolResult through the same ownership gate, drop pending reminders once on the owner's Retry only, and skip observed decisions for side queries. Warn once when model.loopProtection is not a valid mode instead of silently using the default, and update the consecutive-identical comment that still described an unconditional halt. * fix(core): keep reminders across side queries and guard unknown modes A concurrent /btw side query starts as a UserQuery, so its reset cleared the running turn's queued reminders and reminded/observed decisions. Under `warn` the stuck turn then got no reminder at all. Keep that state on a side query's reset, and log each reminder under the prompt id that queued it. Validate model.loopProtection in core Config as well, since SDK and serve embedders bypass the CLI check and an unknown mode acted as `off`. For a stateful read tool with no recorded results, `warn` now records an observed decision at the stop point instead of a reminder claiming the result will not change; `stop` still fails safe and stops. * fix(core): let late stateful results earn the warn reminder Under `warn`, a stateful read that reached the stop point before its results were recorded was observed under the 'identical' key, which then blocked the reminder for the rest of the streak even after the results came back unchanged. Record that case under its own key so the reminder can still be sent once the evidence is in. In daemon sessions, leave queued loop reminders untaken when the repeated tool failure guard stops the batch, so they are not logged as `reminded` without being sent. Update the telemetry docs for `observed` under `warn`, note in model.skipLoopDetection that model.loopProtection decides whether the always-on guards remind or stop, and drop the stale DashScope #5019 rationale. |
||
|
|
b51ce2c305
|
feat(workflow): add per-turn human-origin workflow hints (#14876)
* feat(workflow): add per-turn human-origin workflow hints * fix(workflow): preserve intent across recall and cached submissions --------- Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com> |
||
|
|
f1645116b0
|
feat(serve): add persisted workspace artifact library (#14867)
* feat(serve): add persisted workspace artifact library * fix(serve): align artifact library capability contract * fix(serve): preserve artifact library ownership and replay --------- Co-authored-by: 丁炳智 <dingbingzhi.dbz@alibaba-inc.com> |
||
|
|
a4937a3533
|
feat(web-shell): refresh model catalogs from the model selector (#14685)
* feat(web-shell): refresh model catalogs from the model selector
* fix(web-shell): stabilize model refresh state and capability tests
* fix(web-shell): preserve refreshed model catalogs and runtime scope
* fix(ci): sync upstream build heap budget
Sync the 6144 MB build heap ceiling from upstream PR #14822 (
|
||
|
|
bf118a1220
|
feat(core): report why loop protection stopped a turn (#14858)
Two loop types cover two causes each: turn_tool_call_cap is an explicit model.maxToolCallsPerTurn, the adaptive backstop or a stuck halt past the soft cap, and global_tool_call_duplicate is the heuristic duplicate detector or the duplicate provider tool-call id breaker. Each of these stops now carries an additive loopReason, and cap stops carry the call count and the limit that applied, on the LoopDetected event, the loop_detected telemetry and the daemon turn_error. The SDK keeps the detail on transcript blocks instead of dropping the loop type. The headless CLI, the TUI loop dialog, OpenTUI and Web Shell (en and zh-CN) explain each cause and what to change. No detector fires or stops at a different point. The export renderer budget rises to 2,050,000 for the new i18n keys (+5,139 bytes measured). Part of #14828. |
||
|
|
dc8b523d39
|
feat(web-shell): show background shell task runtime details (#14831)
* feat(web-shell): show background shell task runtime details * fix(build): budget shared shell task runtime labels * test(serve): sync shell runtime telemetry route counts --------- Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com> |
||
|
|
5ded4b8c2a
|
docs(telemetry): clarify OTLP HTTP uses JSON encoding (#14664)
Co-authored-by: 易良 <1204183885@qq.com> |
||
|
|
6ec1b83044
|
feat(web-shell): delete selected message groups from session history (#14297)
* feat(web-shell): add grouped message deletion with durable history updates * fix(web-shell): repair message deletion CI contracts * docs(serve): index message deletion protocol routes * fix(web-shell): harden grouped deletion recovery and export build * fix(web-shell): allow selecting all message groups for deletion * test(cli): observe hook cancellation rejection before waiting --------- Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
798e5c9b66
|
fix(web-shell): preserve slash highlighting in user messages (#14171)
* fix(web-shell): preserve slash highlighting in user messages Fixes #14161 * perf(web-shell): trim slash highlighting bundle overhead Render highlighted text through a plain helper, keep captured line breaks, and only mount the URL renderer for text that can contain an explicit URL. Reuse the existing scoped data attribute for styling. This reduces the transcript JavaScript guard count from 1,332,162 to 1,331,354 UTF-16 units. Preserve host fragment merging and add mixed-case URL and scheduled-task highlighting coverage. * test(web-shell): pin slash-highlight replay path and styling contract * test(web-shell): compare full declaration sets for composer slash parity Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: 铃蓝 <guolinliang.gll@alibaba-inc.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
06efd789b1
|
feat(telemetry): Record reasoning effort on LLM spans (#14718) | ||
|
|
7abcc60348
|
fix(sdk): size session create from the daemon session budget (#14588)
* fix(sdk): size session create from the daemon session budget POST /session answers only after the daemon spawned the session's ACP child and finished its initialize handshake, but the client charged that wait to the generic 30s request deadline. The daemon bounds the handshake with --initialize-timeout-ms (10s by default, 60s on the shared E2E runners), so a slow spawn was abandoned while the daemon still completed it and channel users saw "Could not create task". Restore already sizes its deadline from the advertised limits.sessionRestoreTimeoutMs plus headroom. Apply the same derived deadline to session create and worktree reset, which spawn children for the same kind of work, and keep an explicitly configured client timeout winning. The channel-workers E2E test now also outlives the initialize budget it gives the daemon. Fixes #14584 * fix(web-shell): widen the create watchdog past the daemon handshake budget Session create now waits on the daemon's ACP handshake budget (70s by default) instead of the generic 30s request deadline, so a workspace create action costs one capability preflight plus that larger create request. The watchdog that bounds the whole action still assumed 2 x 30s and would abandon a create the SDK now deliberately allows. Raise CREATE_WATCHDOG_TIMEOUT_MS to cover preflight + create + headroom, and update the deadline tests: the create and preflight budgets are no longer equal by design, a hung create is bounded at 115s, and the SDK request timeout case needs a session delay above the new create budget. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-conflict/jmvdyvii4fo * fix(web-shell): size the create watchdog from the advertised session budget The SDK now sizes a session create from the daemon's advertised session budget rather than the generic 30s request deadline, so a workspace create can span a 30s capability preflight plus a 70s create request. The create watchdog was still a fixed 75s derived from the old "2 x 30s" shape, so the browser reported a create timeout on a spawn the daemon was still finishing -- the bug class the SDK change fixes, one layer up. It also reddened the required Test lane: actions.test.ts guards that the watchdog covers preflight + create + headroom, a floor that is now 115s. Derive the watchdog from the same advertised budget the restore path already reads, so the two clients stop diverging whenever a budget moves, and drop the "keep in sync with DEFAULT_FETCH_TIMEOUT_MS" comment that no longer describes where the number comes from. The guard that asserted a preflight and a create share one budget now asserts what the test is named for: the create budget is sized from the session budget, and the watchdog covers the sum plus headroom. The two neighbouring watchdog tests derive their expected deadline from the same helper instead of hardcoding 75000ms, so the next budget move does not silently re-break them. The restore and load watchdogs are untouched -- they were already derived and still sit at 75s for an old daemon. * fix(sdk): stop a short restore budget from shortening a session create `limits.sessionRestoreTimeoutMs` is the operator's restore knob, and the server returns an explicit `--session-restore-timeout-ms` verbatim with no floor -- only the `--initialize-timeout-ms` branch clamps to the default. Sizing create and worktree reset from it alone meant a legal config like `--initialize-timeout-ms 60000 --session-restore-timeout-ms 5000` gave both routes a 15s client deadline, under the 30s generic request deadline they used before this change, so a create that always worked was now abandoned. Give the two spawn routes their own resolver that floors the derived budget at the generic request deadline. Restore keeps the unfloored budget: pinning it low is deliberate, and flooring the shared helper would have raised it. The Web Shell create watchdog gets the same floor on its create half, so a short restore pin yields a 75s watchdog (30s preflight + 30s create + 15s headroom) rather than 60s, which would have re-opened the gap this PR closes. * docs(sdk): narrow the timeout claims this change falsifies Three statements described create as a short-lived 30s route and the Web Shell creation limit as a fixed 75s, which stops being true once create is sized from the advertised session budget and the watchdog is derived from it. Correct the fetchTimeoutMs option doc, the session-operation resolver's own contract (the advertised field is the restore knob, so for a spawn it hints at the handshake cost rather than bounding it, and a client that creates before reading capabilities still falls back to the 70s default), and the daemon configuration page's Web Shell deadline section -- including scoping the "--initialize-timeout-ms does not raise the create request" claim to the capability preflight, which is the only leg it still holds for. * test(e2e): defer the channel-worker deadline to main #14587 already widened this file's budgets on main (300s ceiling), so this branch's per-test comment would only have lowered it back to 120s. The client fix this PR carries is covered by the SDK and Web Shell unit tests. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmve7t0jzg8 * docs(daemon): correct the create-deadline claims and pin the new budgets R2-1: the Web Shell README section this page links to still documented the replaced 75s fixed action limit, so a host integrator sizing a create against it would tear down a spawn the daemon still completes. R1-6: qualify the initialize-timeout claim with the explicit restore pin, the 60000 floor and the un-read capabilities fallback. R2-3: pin the deliberately unfloored restore route. R2-2: pin the advertised budget reaching the create watchdog through the real call chain. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmvegdno2gr --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
0e2886a844
|
feat(daemon): support session-targeted Skill and Extension refresh (#14050)
* feat(daemon): support session-targeted Skill and Extension refresh * test(daemon): include session refresh in capability baseline * docs(daemon): align session refresh references with contracts * fix(serve): handle selected refresh lifecycle and lookup edge cases --------- Co-authored-by: Ethan Ye <ethanye@U-463KXYWL-1938.local> |
||
|
|
b4614845f0
|
fix(web-shell): delete attachments from an empty composer (#13947)
* fix(web-shell): delete attachments from an empty composer * fix: address review feedback for #13947 * fix: follow up review findings for #13947 * fix(web-shell): preserve draft boundaries during keyboard deletion --------- Co-authored-by: Zhangchenhao-111 <285911435+Zhangchenhao-111@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
808722d9d7
|
feat(web-shell): add side task archive controls (#14300)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Lint & Static (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / web-shell E2E Smoke (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (no-AK, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Blocked by required conditions
E2E Tests / Build for E2E (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 1/2 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 2/2 (push) Blocked by required conditions
E2E Tests / E2E Interactive - OpenTUI renderer (bun) (push) Blocked by required conditions
E2E Tests / channel-plugin E2E (nightly) (push) Blocked by required conditions
E2E Tests / cron-interactive E2E (nightly) (push) Blocked by required conditions
E2E Tests / web-shell Browser Regression (push) Waiting to run
* feat(web-shell): add side task archive controls Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): harden side task archive controls Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(integration): pin session_archive_idle_guard in the serve capability baseline capabilities.ts registers session_archive_idle_guard and server.test.ts already lists it, but the second baseline list in the no-AK integration gate was not updated, so the deep-equal diff failed on exactly that one capability. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-conflict/jmv9k9mxh7h * fix(web-shell): archive and restore side tasks in their owning workspace The side-task archive/restore callbacks called the unscoped client, whose routes resolve ids against the legacy-primary catalog. A task owned by a secondary workspace answered notFound without any state migration, and because both callbacks counted notFound as a successful removal the UI cleared state anyway. Group each request by the task's workspaceCwd and call through the workspace-qualified route (standalone tasks keep the legacy path); notFound now surfaces as a per-session error and the task stays in the catalog. Regression pins: qualified routing for archive and restore with a secondary-workspace task and a standalone task in one call, and a notFound answer that keeps the tab and reports the session id; the three pre-existing archive tests move to workspace-scoped spies. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(web-shell): address review follow-ups - separator, removal-set reset, overview batch archive * docs(design): sync side-task archive design with workspace-qualified routing Review pointed out that the design pair still described the pre-fix contract: it listed not-found among the outcomes that leave the active list, and never stated that archive and restore must be workspace-qualified. That missing sentence is the same omission that allowed the secondary-Workspace bug, so the docs now carry it. Both languages state that each callback groups ids by the task's own workspaceCwd and calls the workspace-qualified route, that ids with no recorded workspace keep the legacy unscoped call, and that a notFound answer now keeps the task listed and reports a per-session error instead of clearing UI state for an operation the daemon never performed. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
a84dbff23e
|
feat(providers): add QwenCloud as the first /auth entry (#14073)
* feat(providers): add 千问AI平台 / QwenCloud as the first /auth entry Adds QwenCloud as a new top-level provider group ahead of Alibaba ModelStudio, following the product ruling in #14025: the two entries coexist rather than one replacing the other, so saved ModelStudio credentials, endpoints and model selections keep resolving to their existing presets and no migration is needed. Two new presets share the ModelStudio wizard shape (Access Method -> Platform -> API Key -> models): - qwencloud-standard (pay-as-you-go, `sk-` / `sk-ws-` keys) - qwencloud-token-plan (Credit subscription, `sk-sp-` keys) Each offers China (千问AI平台) and International (QwenCloud) as an explicit platform choice, so the UI language never decides the endpoint. Coding Plan is not offered here — it is a ModelStudio product. Endpoints were taken from the platform's own documentation and probed before landing (all four return the DashScope `invalid_api_key` error envelope): - https://maas.qianwenaiapi.com/compatible-mode/v1 - https://token-plan.maas.qianwenaiapi.com/compatible-mode/v1 - https://maas.qwencloudapi.com/compatible-mode/v1 - https://token-plan.maas.qwencloudapi.com/compatible-mode/v1 The new hosts are added to the DashScope provider detection and the web-search endpoint allowlist so the built-in `web_search` backend keeps working; both new env keys are added to the no-AK CI gate's cleared-env list. Group wiring is mirrored across the ink and OpenTUI dialogs, the VS Code provider picker, and the serve catalog consumed by Web Shell. Closes #14025 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): add QwenCloud auth strings to en and zh-TW locales The new auth entries introduced in the previous commit were only added to zh.js. Since en.js is the source of translation keys and both zh and zh-TW enforce strict key parity, the i18n check failed with "Extra key in zh.js" for all eight strings. Add the same keys to en.js as identity mappings and to zh-TW.js with Traditional Chinese values, so all three locale files have an identical key set. * test(cli): follow the QwenCloud row in the OpenTUI auth menu tests The new first /auth entry shifts the main menu by one row, but the tests addressed the sub-menus by position, so most flows landed a row early and the region-height assertions still named the old first row. * feat(providers): localize the QwenCloud /auth entry label per locale The top-level /auth entry was the only mixed CJK/Latin label in the menu ("千问AI平台 / QwenCloud"), and because en/zh mapped that key to itself it was effectively hardcoded mixed in every locale. Route it through i18n instead: en and other locales show "QwenCloud", zh shows "千问AI平台", zh-TW shows "千問AI平台". Locale-neutral surfaces (serve catalog, VS Code provider picker) carry the single Latin brand; the Chinese brand stays visible in the explicit platform step ("China — 千问AI平台") and the docs. The step-title flowTitle now goes through t() so the localized brand also appears in "… · Step 1/3 · …". Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(providers): register QwenCloud hosts in telemetry and reasoning classifiers The QwenCloud MaaS hosts and env keys were registered in the two host gates this PR edits, but two further independent classifiers of the same hosts did not learn about them, and both misclassify the entry this PR makes the first-position default: - telemetry/gen-ai-provider.ts had no providerFromHostname match for maas.qianwenaiapi.com / maas.qwencloudapi.com and no PROVIDER_BY_ENV_KEY entry for QWENCLOUD_API_KEY / QWENCLOUD_TOKEN_PLAN_API_KEY, so resolveGenAiProviderName fell through to its `default: return 'openai'` arm. Every span and log record for a QwenCloud install was attributed to OpenAI, while the identical ModelStudio path resolved to 'dashscope'. - core/reasoning-overrides.ts keeps its own inline hostname regex without the new hosts, so a user-authored partial reasoning override on a qwen* model behind a QwenCloud baseUrl selected 'qwen-chat-template' -> toggleOnly -> invalidReasoning, which captureLatestReasoning catches and warns. The override was silently discarded; the same declaration on dashscope.aliyuncs.com resolves to 'dashscope-effort'. Also widen the SDK-side mirror DaemonAuthProviderCatalog.groups[].id to include 'qwencloud', which packages/cli/src/serve/types.ts and auth-provider-helpers.ts already emit as the first group, and derive web-shell's AuthGroupId from that union instead of re-declaring it, so the SDK type is the single source of truth. A second literal union in web-shell is why nothing reddened in-repo while the daemon emitted a group id its own published wire type declared impossible. The new suffixes go inside the existing anchored (^|\.)...$ group, so a forged maas.qianwenaiapi.com.evil.com cannot match while the legitimate token-plan. subscription subdomains still do. Both env keys map to the existing 'dashscope' provider value rather than a new one. The hosts stay literals in gen-ai-provider.ts rather than importing QWENCLOUD_MAAS_HOSTS: that module is a zero-import telemetry leaf, and dashscope.ts already imports core/reasoning-overrides at runtime, so importing back would cycle. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmv8fwg2z5f * test(providers): pin QwenCloud host classification and registry membership Cover behaviour this PR added that no test observed: - dashscope.test.ts: the QwenCloud MaaS pay-as-you-go and token-plan hosts are DashScope origins, plus dot-boundary lookalike negatives. - web-search.test.ts: the same four hosts pass the web-search gate. - qwencloud-standard/token-plan.test.ts: both presets are registered in ALL_PROVIDERS and QWENCLOUD_PROVIDERS, and the qwencloud access-method group excludes Coding Plan (replaces a constant-true id/label check). Each new assertion is mutation-verified: dropping isQwenCloudOrigin from the dashscope return, weakening its host match, dropping the QWENCLOUD_MAAS_HOSTS spread, removing the two registry entries, or flipping codingPlanProvider uiGroup each turns exactly the intended test red. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmv8mbxgn5t * test(providers,cli): pin QwenCloud preset separation and the auth catalog group Coverage for lines this PR itself added (review round 1: R1-21, R1-26, R1-15): - qwencloudStandardProvider.ownsModel is cross-checked against its QwenCloud Token Plan sibling and against a foreign envKey on its own endpoint. The two presets share uiGroup, catalog and protocol bucket, so that envKey gate is the only discriminator; dropping it kept the suite green while applyModelProvidersPatch would claim a Token Plan install. - buildAuthProviderCatalog gets its first test: the QwenCloud group is first and carries both access methods. A misspelled group filter stayed green. - The zero-row main-menu paint test now names row 0, which this PR re-pointed from Alibaba ModelStudio to QwenCloud while only rows 1 and 2 stayed pinned. Each assertion was confirmed load-bearing by one mutation that the pre-existing tests did not catch. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmv8oh38g5y * docs(users): sweep the /auth menu enumeration in overview and quickstart This PR re-numbered the /auth menu to four options in auth.md and model-providers.md, but overview.md and quickstart.md still described three entries with Alibaba ModelStudio as the recommended one (review round 1: R1-4). The doc set contradicted itself, and a quickstart reader holding a QwenCloud key was routed to the ModelStudio entry, whose endpoints reject it. Both pages now list QwenCloud first and reserve the "recommended setup" claim for it, matching auth.md. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmv8oh38g5y * fix(providers): validate daemon credentials and close QwenCloud follow-ups (#14302) * fix(serve): validate API key format before persisting in daemon install path The CLI, ACP and VS Code surfaces each call provider.validateApiKey before persisting credentials. The daemon route (POST /workspace/auth/provider) did not, because validateApiKey is a function on ProviderConfig and cannot be serialized to the wire — so no client of this route can apply the check either. Without this guard a key the provider's own entry rejects (e.g. a Token Plan sk-sp- key aimed at the Standard API Key entry) gets persisted silently, and the user only discovers the mismatch at the first authenticated call. Fix: call validateApiKey in the route after protocol validation and before invoking installAuthProvider. Return 400 with code 'invalid_api_key' on failure, matching the other three surfaces. Closes #14257 (item S3 / R2-13). * fix(providers): address #14257 deferred Suggestions (D1/D2, T1/T3-T9, I1, P5, S6/S7) Batch fix for actionable items from #14257: Docs: - D1: Add settings.json example to QwenCloud headless setup section - D2: Add QwenCloud rows to web-search.md auto-activation table Tests: - T1: Pin flowTitle locale resolution in both dialog suites - T3/T4: Assert QwenCloud sub-menu contents (Token Plan + Standard, no Coding Plan) - T5: Add missing token-plan.maas.qwencloudapi.com endpoint to dashscope.test.ts - T6: Add QwenCloud host tests to reasoning-overrides.test.ts - T7: Assert providerState in qwencloud-standard preset test - T8: Test ownsModel baseUrl conjunct independently of envKey - T9: Test providerMatchesCredentials URL gate independently of envKey - S7: Assert QwenCloud group in VS Code AuthMessageHandler picker i18n: - I1: Add validateApiKey messages to all 9 locale files Product: - P5: Fix qwen3.7-plus modalities to include video (matches alibaba-token-plan) Code: - S6: Update docstring enumerations in dashscope.ts and web-search.ts Remaining items need human decisions: P1-P4 (wording/labels), S1-S5 (API surface), T2 (ink suite gating is a pre-existing harness pattern). * fix(auth): translate provider titles at render time Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(providers): handle optional preset models Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(providers): pin Token Plan providerState and ownsModel baseUrl leg Addresses four review Suggestions on this PR: - R1-10 (#14257 T7): the providerState stamp was asserted for the Standard preset only. Token Plan has two baseUrl options and a baseUrl-dependent modelNamePrefix, so stamp both routes. - R1-11 (#14257 T8): the deciding baseUrl negative for ownsModel was added to the Standard suite only. The Token Plan negatives added here differ in envKey, so the baseUrl conjunct was never the deciding factor. - R1-1: the web-search host note contradicted the two QwenCloud rows this PR adds to the table on the same page. Name the Model Studio and QwenCloud MaaS families the code actually accepts. - R1-4: the daemon route comment claimed parity with the CLI/ACP/VS Code copies. Those resolve the endpoint first and validate the resolved URL; this route validates the client-supplied baseUrl. Comment only. Both new Token Plan rows were mutation-checked one mutant at a time: dropping baseUrl from resolveProviderState, stamping the provider default endpoint instead of the requested one, and relaxing ownsModel to envKey-only each turn exactly the intended row red. An additional cross-route "versions must differ" row stayed green under all of them (model configs already embed baseUrl), so it was dropped rather than shipped inert. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmv9zmmcj86 * test: make the QwenCloud witnesses discriminating; trim daemon key once R1-9: the two Token Plan cases and the four lookalike cases never reached the dashscope host regex - both blocks short-circuited in validateReasoningDeclaration/validateReasoningCapabilities before route.baseUrl was parsed, and the block comment misstated the Token Plan endpoints as unregistered. Fold the Token Plan hosts into the QwenCloud host table with a declaration that omits `thinking`/`profile`, so all four registered endpoints now pin the anchored suffixes. R1-5/R1-6: the Ink VIEW_TITLES fix had no CI-executed guard (every keystroke case is skipped under CI=true) and its T1 case asserted only the already-lazy flowTitle path. Add one ungated, keystroke-free render assertion and assert the sub-menu heading between the two keystrokes. R1-3: validate and forward the same trimmed key rather than validating the trimmed value while forwarding the raw one. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmva623ox8i * fix(auth): guard zh locale test cleanup, note hand-written provider caveat R1-7: wrap the QwenCloud zh flow-title test body in try/finally so unmount() and the locale reset still run when an assertion rejects, matching the shape of the opentui twin added in the same change. R1-2: note in the settings.json alternative that a hand-written provider entry carries no capabilities stamp, so /effort offers the generic tiers rather than the QwenCloud curated set. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmvaacf8g8p * fix(providers): address #14073 round-3 deferred Suggestions - docs/design/auth/motivation.md: QwenCloud-first /auth enumeration (S1) - docs/users/quickstart.md: name QwenCloud credentials in Before you begin (S3) - docs/users/configuration/auth.md: scope the Token Plan prefix rule to ModelStudio vs QwenCloud so Option 1 and Option 4 no longer read as contradictory (S4) - qwencloud-standard.test.ts: correct the ownsModel comment — envKey and baseUrl conjuncts each exclude the sibling independently (S5) - auth-provider-helpers.test.ts + alibaba-standard.test.ts: pin every catalog group's providerIds and the alibaba uiGroup (S6) - web-search.test.ts: forged-suffix boundary cases for the QwenCloud host gate (S7) S2 (6-latin-locale backfill) and S8 (web-shell group dict) verified as no-ops: the sibling ModelStudio strings rely on the identical raw-key fallback in the same locales/dictionary. * fix(auth): localize provider API key errors * fix(providers): seed Token Plan only with models its endpoint serves A live GET /models on the China Token Plan route (real subscription key, 2026-10-16) serves neither kimi-k3 nor deepseek-v4-pro-0813 — the endpoint offers kimi-k2.x and deepseek-v4-pro instead, matching the platform doc's personal-edition table. The shared QWENCLOUD_MODELS seed list therefore pre-filled two ids the wizard would install as dead entries whenever discovery succeeded, and offered them in the offline fallback. Filter the shared catalog down to the five confirmed-served models for the Token Plan preset instead of duplicating stamps; the Standard (pay-as-you-go) catalog keeps the full list, which no live data has falsified. Pinned by a new preset test asserting the exact seed ids. --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(auth): preserve explicit models during provider discovery --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
87de74860b
|
docs(tools): add looot to the web search MCP services (#13999)
Co-authored-by: 易良 <1204183885@qq.com> |
||
|
|
87dcc4a708
|
feat(web-shell): add file-tree deletion with confirmation (#14158)
* feat(web-shell): add confirmed file deletion from file tree * test(serve): include file deletion in capability baseline * fix(web-shell): address file deletion review suggestions * test(cli): synchronize child cancellation tests * test(web-shell): refresh transcript budget after file actions --------- Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
17d5f774ea
|
feat(web-shell): add file creation and confirmed folder deletion (#14169)
* feat(web-shell): add file creation and confirmed folder deletion * fix(web-shell): address file tree review and transcript regression --------- Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: 易良 <1204183885@qq.com> |
||
|
|
74033d0bd2
|
fix(sdk): release reconnect transports created after disposal (#13864)
* fix(sdk): release reconnect transports created after disposal * fix(sdk): preserve closure errors during reconnect cleanup * test(sdk): satisfy lint in reconnect disposal coverage * test(sdk): model reconnect subscription failures during iteration |
||
|
|
1ce7d71633
|
fix(core): preserve fuzzy edit line boundaries (#13484)
* fix(core): preserve blank lines after fuzzy edits * test(core): cover repeated fuzzy edit matches * fix(core): keep fuzzy edits within whole-line matches |
||
|
|
3319e819b9
|
fix(sdk-python): resolve Windows command shims on the effective PATH (#13862)
* fix(sdk-python): resolve Windows command shims on the effective PATH * style(sdk-python): format Windows shim regression assertion * fix(sdk-python): secure Windows command lookup and cleanup * style(sdk-python): align test import grouping with CI --------- Co-authored-by: 易良 <1204183885@qq.com> |
||
|
|
7326ac6e0b
|
Add SerpApi to web search MCP services list (#13104)
* Add SerpApi to web search MCP services list * Updated best for, added a information that is sent to SerpApi. * Moved the information sent to SerpApi part in the description to align with the format followed by other listings. |
||
|
|
c3c38cf583
|
feat(web-shell): bridge trajectory history and live lifecycle state (#13952)
* feat(web-shell): bridge trajectory history and live lifecycle state * fix(web-shell): preserve trajectory recovery and bound transcript bundle * fix(acp-bridge): preserve request lifecycle frames across compaction * fix(web-shell): address trajectory recovery review findings --------- Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com> |
||
|
|
008c0de746
|
docs(sdk): clarify empty core-tool allowlists across SDKs (#13949)
Co-authored-by: Zhangchenhao-111 <285911435+Zhangchenhao-111@users.noreply.github.com> |
||
|
|
f8c99414b2
|
fix(core): report monitor startup failures as tool errors (#13460)
* fix(core): report monitor startup failures as tool errors * fix(core): classify monitor startup failures consistently |
||
|
|
23274922f5
|
feat(web-shell): add Send now for queued mid-turn messages (#13681)
* feat(web-shell): add Send now for queued mid-turn messages
A message typed while a turn runs waits for the model response that is
streaming and for its tool batch, so a correction typed into a long answer
reached the model seconds later, and Stop was the only faster path.
Typing keeps that behavior: the message stays queued, editable and
deletable until the next tool boundary. A new Send now action on a queued
row asks the daemon to deliver the queued messages at once.
POST /session/:id/mid-turn-messages/send-now, advertised by
session_mid_turn_send_now, sends the agent craft/midTurnSendNow. While a
foreground turn streams a response that has not started a tool call, the
agent drains the user's messages, cuts the response short, keeps the part
already streamed, and answers in the same turn. Running tools are never
interrupted, a response that started a tool call is never cut, queue-only
steering is not taken early, and a request is scoped to the turn that
received it. Input taken by a turn that is then stopped or fails is kept
in that turn's conversation.
The TypeScript SDK gains sendMidTurnMessagesNow on both clients.
* fix(web-shell): address review findings on Send now
- Restart the Stop-hook state when a continuation answers input that
cut its response short, so the next Stop check is not hook-forced and
the consecutive-block count and reasons start over.
- Give input a finished turn keeps unsent a 2 s resolution deadline of
its own; the deadline also cancels media bridges.
- Requeue a failed send-now drain in its original order, and log a
send-now request the agent rejects.
- In Web Shell, reconcile a row whose message already left the queue
and ignore a repeated click while a request is out.
- State the tool-call carve-out wherever Send now is described, and
document goal turns, the narrow no-answer window and token accounting.
- Add tests for the review's gaps, drive the drain deadline with fake
timers, and add the route to the multi-workspace failure tables.
* fix(web-shell): keep send-now input a stopped tool boundary leaves unbuilt
When a turn is stopped while a tool-boundary drain resolves a message,
the drain now hands back the messages it has not built that an early
send-now drain took, so the end-of-turn keep records them. Input the
boundary drain took for itself is still dropped there, as before.
- Tests: the stopped boundary keeps that input once and carries nothing
into the next turn; a Goal continuation is cut; a background
notification turn is not; a failed send-now drain does not requeue a
message that left the queue meanwhile.
- Bridge and SDK contracts: the cut applies to a foreground turn's
streaming response, and `requested: true` does not mean a cut.
- Protocol and design docs: the timed-out send-now drain, resending a
settled id, the 10-message drain limit, and the tool-boundary drop.
* test(web-shell): pin send-now behavior questioned in review
- Assert that cron and background-notification turns never open their
response to interruption and that a send-now request arms nothing
while they stream.
- A response cut short by queued input still answers that input when it
ends with an error, while an error from a response its drain did not
cut remains the turn's error. Explain in `watch` and the design doc why
the cut response's error is absorbed: the cut does not always arrive as
an AbortError, and the response was given up.
- A message whose deletion missed while a failing drain held it is still
delivered, which matches the `{ removed: false }` answer the client
got. Document this in the protocol reference.
- Rename a requeue test to say it covers a queue-only entry.
* docs(web-shell): scope the abort invariant and drain-held removals
- The main prompt loop's catch states that other AbortErrors surface.
Note that a response cut short by queued input reaches that catch only
when the turn itself is stopped meanwhile, because `watch` absorbs
whatever a cut response throws.
- In the force-drain paragraph, add the drain-held case of
`{ removed: false }`. Such an id must not be re-posted, and its listing
in `settledMessageIds` is not final, because a failed drain returns it
to the queue without an event.
|
||
|
|
e31d5500f1
|
feat(managed-agent): H6a schedule and automation run record contract (#13536)
* feat(managed-agent): H6a schedule and automation run record contract The H6a slice of #12827, ahead of any producer: both bodies register and replay in TypeScript and Java while both domains stay disabled for submission. - managed-schedule v1 (chain scheduleId): owner scope, bounded goal, a five-field cron with an IANA timezone, append-only definition revisions (each successor exactly +1) with their digest, the prompt resource reference, the persistent/per_run target mode with the target frozen exactly for persistent, the skip/queue_one/allow overlap and none/latest/bounded catch-up vocabularies (the limit set exactly for bounded), an enabled flag, and a purely logical run block that freezes the definition for good once terminal. - managed-automation_run v1 (chain automationRunId): the pinned definition revision, the closed occurrenceKey union — schedule:<slot> as a canonical UTC instant to the second (open question 1 answered), manual:<commandId>, webhook:<eventId> registered and refused — the frozen sessionMode/target, and the H0b run block: a durable dispatchId from the first revision, no executionCallId or definition pin, the delivery line past planned only once the run ended so a delivery never re-runs the model. - The shared managed-automation-record/1 fixtures pin keys, fixed keys, vocabularies, 56 body cases and 22 successor transitions, replayed by the TypeScript gate and the Java contract test; the seven timezones anchor in each host tz database. - The projection registry and its v1 fixtures grow the two bodies under additionalRecordBodies, as do the Java RECORD_BODIES; the domains stay out of MANAGED_SESSION_ENABLED_DOMAINS and commitExtensionRecord still refuses them — enablement is the H6b producer slice's explicit step. Design: docs/design/2026-10-04-managed-automation.md (bilingual update). * fix(managed-agent): close the H6a review round's contract findings Round-1 review of the H6a record contract (#13536): one Critical, the rest Suggestions, each verified against this head. - R1-1 (Critical): Java boundedText charged one byte per unpaired surrogate (the default encoder folds it to '?') while Node's Buffer.byteLength charges the three of U+FFFD, so the two validators disagreed on identical wire bytes for a 1366-surrogate goal. Java now charges the same three bytes per unpaired surrogate. Shared boundary cases pin both sides (1365 accepted, 1366 high and low refused). - R1-6/R1-7: the cron validator now checks per-field atoms semantically — digit/list/range/step atoms only, values inside each field's bounds, steps within 1–max, ranges never wrapping — with one honest refusal message per rule instead of one message for all. Fixtures pin */0, */60, - - - - -, 99 99 99 99 99, 0 24 and 17-9. - R1-8: the timezone refusal no longer claims IANA validation; the contract is shape-only on purpose (hosts disagree which names resolve), named in the body comment and the design document. - R1-9: the seven anchored timezones now replay through the validator as body cases, so the host tz anchors witness implementation behaviour, not only host-DB membership. - R1-11: Java runs the catchUpLimit value check before the bounded pairing rule, the same order as TypeScript. - R1-12: pinned that an automation_run task row reads definitionRevision null (the run block holds no definition pin); the authoritative revision lives on the record — asserted in both contract suites and named in the design document. - R1-3: design documents synced to the shipped contract in both languages (occurrence identity, automationRunId chain identity, terminal freeze, count_limit framing, delivery target, per_run target, provenance marker, DST canonicalization tables reassigned to H6b). Not changed, with replies on the threads: promptRef store verification stays out of the record contract (cross-scope reference, resolved at H6b definition admission), and timezone names stay shape-checked (host tz databases disagree; semantic resolution pinned to H6b admission). |
||
|
|
8505479d87
|
docs(serve): fix stale permission-route note and document pending-prompt events (#13281)
docs/users/qwen-serve.md still described session-scoped permission routing as Stage 1.5 future work and pointed integrators at the legacy process-global vote route. Rewrite the note: the session-scoped route ships (Wave 4 / F3) and is the recommended form for new multi-workspace integrations; the legacy route remains for pre-F3 single-workspace clients with its 404 semantics spelled out (the same body as a lost vote for a session owned by another runtime). Add event-reference entries for pending_prompt_added / pending_prompt_started / pending_prompt_completed to docs/developers/daemon/09-event-schema.md: payload fields, the state union, the idle-session no-event caveat, and the "queue-view bookkeeping, not a turn terminal" guidance (correlate turn_complete / turn_error by promptId for completion). Derived from the field notes and triage in #13279. |