mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-10-21 22:39:45 +00:00
420 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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. |
||
|
|
4970bfa172
|
feat(managed-agent): adopt the next Hosted Harness generation (G3) (#13174)
* feat(managed-agent): adopt the next Hosted Harness generation (G3) A Hosted Session no longer dies with the Harness process generation that first served it (#12952 G3, Steps 1+2). On a generation signal the control plane renegotiates once through the connector instead of failing every bound Session until its own restart; pending Turns re-attach through the takeover load as their retries come due. Takeover refusals split into a typed terminal decline (deterministic in the journal) and the existing retriable code; the takeover load is idempotent against a lost reply and gets its own timeout; negotiation requires the journal-contract feature token so a rolled-back Harness is refused once; a marked-but-never-admitted Turn withdraws its submission mark, backstopped by journal commandId idempotency. The failover runner gains a Harness-only restart arm on all three scenarios (harness_boot_id moves with no Java restart) and a SIGSTOP/SIGCONT frozen former-owner fencing arm, wired into hosted-harness-mysql together with the previously local-only basic mode. Design docs included in English and Chinese. * fix(managed-agent): tolerate the bootstrap 404 of a restarting Hosted Harness The serve delegating app answers every routed path with a bare pre-contract 404 while its runtime is still starting. The Harness-restart arms raced that window: a prompt submitted milliseconds after the new process bound the port could meet the bare 404, which the client read as a generation contract violation and ended the Turn as hosted_harness_protocol_error without any retry. A missing boot header on 404 now maps to a transient transport error so the coordinator retries through the window, and the failover runner waits on deep health (?deep=1) before driving turns into a restarted Harness. Terminal turn failures now also log their underlying exception. * fix(managed-agent): freeze only the Harness writer in the former-owner fencing arm SIGSTOPping both original owners left the original Spring's workspace binding unreclaimable: the reclaim proves death from /proc liveness, and a stopped JVM still reads as alive there, so the replacement's reconcile timed out (runtime_broker_reconcile_timeout) and the Turn never completed. The arm now stops only the original Harness (reclaiming its workspace binding requires the Spring to die anyway — the continuation mode's crashProcess), completes the Turn with a full replacement, then wakes the frozen writer and asserts the journal, transcript, and session binding are untouched. Design D7 updated in both languages with the discovered boundary: resource-level takeover of a surviving owner is structurally out of reach today, which is exactly what this issue's sequential-generation scope excludes. * ci: retrigger sdk-java workflow after event delivery stall * fix(managed-agent): pin the fencing proof on writer identity, not head revision The first merge-base run of the freeze arm failed its own gate: after waking the frozen Harness the journal head moved (rev 30 -> 45) while the writer generation, boot binding, transcript, and terminal count all held. The moved revisions were the replacement's own legal churn, not old-writer mutation, so the head-equality assertion conflated the two. The exit-check claim is identity: no transaction out of the old writer generation after it wakes. Count that straight off qwen_managed_session_journal_tx (which carries writer_generation), keep the binding/transcript/terminal assertions, and align design D7 in both languages. The error string now also reports the old-writer tx count. * fix(managed-agent): qualify the journal tx schema in the freeze-arm gate The identity-based old-writer count queried qwen_managed_session_journal_tx unqualified; the runner's MySQL connection has no default schema, so the arm died with ERROR 1046 instead of evaluating the fence. Qualify it as qwen_managed_agent.qwen_managed_session_journal_tx like every other query in this runner. No behavior change to the assertions themselves. * fix(managed-agent): harden takeover correctness from the R1 review - settle turn_settled checkpoints through the load route's own tail instead of a terminal decline, and keep transient store verdicts (missing_state/missing_checkpoint) retriable on both authorization reads - exempt only takeover-shaped 409s from the pre-admission retry budget; every other failure kind meets it - scope the 120s load-timeout to recovery loads so plain attaches under the CHM bin locks stay on 30s - adoptGeneration: move map churn out of the connector monitor (lock-order inversion), evict only stale-boot entries on equal-boot mismatches - answer a duplicate submit of an already-settled prompt with a 202 replay anchored at its input.accepted sequence - withdrawSubmissionAttempted: guard on the same status window as its peer CAS methods; complete capability mismatch terminally in both remaining coordinator layers - runner: freeze-arm pre-freeze liveness assertion, /health-proven wake non-vacuity, teardown SIGCONT, exact-string dispatch-generation bounds - CI: job ceiling 95 with corrected ceiling math; pin all seven failover arms in hosted-process-ci.test.js - sync both design-doc languages with the shipped behavior * fix(managed-agent): close the R2 review round on the replaceable-host-harness slice - withdraw + cancel interference (R2-1, Critical): a CANCELLING Turn whose never-admitted mark was withdrawn is fast-cancelled instead of being re-dispatched into a second live execution at the adopted generation - the generation-mismatch catch escapes the pre-admission budget only on a bound Session, like every sibling arm (R2-3) - bound the remote-supplied decline reason to the error_message column so the terminal write itself never throws (R2-5) - the last fail(...) in the catch ladder is failTerminally with the exception logged, like its siblings (R2-6); the withdrawal itself is logged too (R2-7) - freeze arm: the replacement Spring inherits the original port so the woken writer meets a live fencing control plane, not a dead socket (R2-2); the wake block asserts the head actually advanced past the frozen owner's writer generation, drops the wrong heartbeat claim (R1-8 follow-up), separates the two disk-audit meanings, and asserts the retained Harness home (R2-8) - the hosted /capabilities feature list derives from the registry instead of a hand-maintained literal (R2-9) - docs: D7 scoped to the journal half of the Q2 exit check, the binding half and the pre-contract-404 classification recorded in Boundaries (R2-4, R2-10) * docs(sdk-java): correct the load-timeout coverage comment in the client test * fix(managed-agent): reference the frozen Harness through the children registry in teardown The R1-4 teardown SIGCONT referenced the try-block-scoped `harness` binding from the finally block, so every freeze-arm run died with ReferenceError after all fencing assertions had already passed (CI run 37009843147). Reach the child through the children registry by its start() name instead. * docs(managed-agent): scope the adoption sentence to this slice's takeover reach * fix(managed-agent): close the R3 review round at its five shared roots - retract the turn_settled settle tail to a typed decline: the tail answered 200 with no recovery watermark (Java then failed the bind, orphaned a completed journal turn), fabricated state:'completed' on phase alone, ran mutating work under a passive load, and retained no snapshot for the lost-reply retry. D10 already owns rebind-and-keep-reading as the Step 3 row (R1-25-re, R3-2 .. R3-5) - the load route's restore guard applies the same durable-vs-transient verdict read, because the bundle collapses both into one blocked bit and the durable decline was unreachable from production (R1-26-re) - the pre-admission exemption keys on lease-shaped 409 codes only: configuration-shaped 409s (e.g. hosted_tool_profile_conflict) meet the budget, and the same 409 exempts nothing on an unbound Session (R1-1-re, R1-30-re, R3-13) - equal-boot adoption keeps pendingRecovery: the markers were minted by the same live client (R1-29-re) - lifecycle capability mismatch: revert completing-as-unconfirmed — that skipped the drain, flipped the session, and recorded a clean row with no FAILED vocabulary to tell it apart; the reason-loud retry is the honest end of the line until an operator realigns versions, and the README sentence says so. Action-response outbox keeps its honest FAILED-with-code terminal (R1-45-re, R3-5, R3-15) - test hardening: concurrency close-once now surfaces worker assertion failures via Future.get and pins that no real client build happened (R3-12); loadTimeout-vs-requestTimeout discriminated at the call site (R3-9); single-walk acceptedInputSequence (R3-18) - runner: 5-column execution queries pin runtime_session_id unchanged under --harness-only (R3-14); frozen-arm teardown looks the child up by a shared label constant and fails the run on a drift (R3-17) - CI: ceiling 105 covers the nine summed step ceilings (92) plus the uncapped setup steps (R3-6); the seven-arm pin also asserts each script's flags and the install step's existence (R3-16) - docs: both languages re-synced (D5 retraction, D9a code allow-list, D7 teardown, D8 runtime identity, honest validation digests, the four missing changes-table rows, README retry clause) * fix(managed-agent): replay takeover snapshots only to a same-shape request A passive load only asks broker.status(), so its snapshot keeps phase='await_runtime'; replaying it to a drive load handed the coordinator a checkpoint that isContinuationReady() can never accept, terminally failing an otherwise continuable Turn. Record the request shape with the snapshot and replay only on a mode match; the clear conditions stay promptId-only. Also make the adoption race test deterministic: a package-private createClient() seam lets the test inject the replacement instead of a real /capabilities call (proven flaky on CI), worker assertion failures now surface through Future.get, and the pendingRecovery fixture seeds its marker through the recovery path. * fix(managed-agent): close the self-audit round on the replaceable-harness slice Three rounds of undirected plus adversarial audit over the whole PR diff (31 files), with three parallel deep-dive passes over the Java, TypeScript and runner/docs surfaces. Every finding was re-verified against the tree before acting; five reported defects were rejected on evidence. Correctness: - the load route read the blocked-restore verdict AFTER managed.close(); sealing the journal makes every later store read fail as "writer is not active", which the authority erases into missing_state, so the durable branch this PR added could never fire and a corrupt checkpoint stayed in the retry-forever class. Read the verdict first, and pin the order with a witness that goes red when the read moves back (the local authority's seal does not disable reads, so only the ordering assertion catches it). - drop hosted_session_already_attached from the lease-bounded 409 set: the daemon removes an attachment only on an explicit detach or delete and this control plane never detaches, so a Spring restart against a surviving Harness retried a permanent refusal forever instead of ending the Turn. - adoptGeneration evicts by boot identity in both branches instead of clearing, so a marker minted by a concurrent rebuild can no longer be erased between two adjacent clears (which left a live attachment with a null recovery report), and markers follow their attachment. - the post-withdrawal fast-cancel reads the row again: a cancel landing after the claim is invisible in the claimed snapshot, and acting on it submitted a live execution only to cancel it below. - a durable verdict re-read after settling now declines as checkpoint_blocked, matching the identical pre-settle classification. - an unrecognized blocked reason throws instead of declining, so a reason core adds later cannot silently become a terminal Turn failure. - the operator-settable load-timeout is validated at connector construction; a bad value previously threw on every client() call and surfaced as an endless transient retry naming only the exception class. Tests: already_attached and lost-withdrawal-CAS witnesses; the fast-cancel witness now pins the re-read (cancel after claim); the concurrent-adoption test surfaces worker assertions through Future.get and injects the replacement through a package-private createClient() seam instead of standing up a live /capabilities call; the CI pin gains each arm's mode flag and a ceiling-sum invariant. Docs (both languages): D1 now describes the boot-identity eviction and lists what this slice really added to HostedHarnessClient; D3 no longer claims the bind-refusal path stops being reached (it is D4's trigger); D6 records the same-shape replay clause; D7's fence rationale no longer blames heartbeats (lease renewal touches writer_lease_until only); D9a names the two exempt codes and why already_attached is not one; the validation digest states which claims are pinned and which are not; the CI arithmetic reads 12 + 8x10 = 92; the stale workflow nit is dropped. * chore(ide): regenerate NOTICES for the merged dependency set (email channel) * fix(ide): replace the workspace-polluted NOTICES with the pristine regeneration The file committed in |
||
|
|
f2e069061a
|
feat(web-shell): add adaptive navigation rail and unified Live settings (#12943)
* feat(web-shell): add adaptive navigation rail and unified Live settings
* style(web-shell): add space below the Home action section
* Revert "style(web-shell): add space below the Home action section"
This reverts commit a2e1c7622336cceb174faf1814b057a727f6a589.
* fix(web-shell): Preserve navigation and Live drafts across refreshes
* fix(web-shell): keep rail navigation state, drag widths, and Live settings drafts consistent
Address the Critical findings from the latest review rounds:
- Build the standalone shell sidebar config from the sidebar's exported
defaults so the Agents entry can no longer silently drop out.
- Gate rail navigation's persisted uncollapse behind restoresColumn so
opening a full page (Settings, Plugins, ...) no longer rewrites the
user's collapse preference; only Home/Channels/Live restore the column.
- Reset the sidebar section when a panel closes and stop leaking 'split'
as a sidebar page, so a wide split keeps the home column and a working
collapse control.
- Clamp drag widths to the container-aware cap the CSS already renders,
floor the visual drag at the layout minimum, and restore the persisted
width from state instead of a mid-animation/container-capped rect.
- Cap the compact drawer's min-width at the container width.
- Keep the Live voice trigger reachable from the Live page header when
the sidebar's slot is hidden.
- Settle Live settings draft fields (and their conflict baselines) that a
refresh converges onto, so a later edit is not blocked by a stale
baseline.
* fix(web-shell): keep rail panel sections, status dots, and Live drafts consistent
Address review findings on the navigation-rail change: retain the
Channels/Live sidebar section when its panel closes (an explicit Home
choice sticks on the first click), load secondary-workspace attention
status whenever the Home column is hidden (idling the poll when nothing
runs), ignore zero-displacement presses on the resize handle, settle
padded Live settings drafts on trimmed refresh convergence, disable the
shortcut capture until status loads, key the empty-chat welcome chrome
off the shell container signal, and document /live in the pre-auth path
lists.
* fix(web-shell): close rail-era gaps in shortcut, dock, and status handling
Address the round-5 review findings on the navigation-rail change. The
Cmd/Ctrl+B handler resolved its target with e.target, which shadow-DOM
retargeting replaces with the portal host, so the editable-target
exemption never fired for portaled dialogs; it now reads
composedPath()[0] like its siblings. The same handler toggled the raw
sidebarCollapsed state, silently inverting the persisted preference in
the split-view fold band where the sidebar renders folded regardless;
the shortcut, the sidebar prop, and the environment-panel dock budget
now share one effective-collapsed expression. The rail's 56px is
excluded from the dock budget so the environment panel keeps docking at
1440px windows instead of being force-closed on load, with the e2e step
re-pinned at 1440 and the consequence recorded in the sidebar docs. The
secondary-workspace status queries now stay active while the Live
section column is open (the rail Home button is the only status surface
there), and the rail branch resets the project search when Live unmounts
its autoFocus field so a Home round-trip cannot steal focus. The Live
Voice card gates key removal and the required marker on the saved key
state, so a model edit leaving a route can no longer offer removal of a
key that was never stored.
* fix(web-shell): stop silent collapse-preference and drag-width rewrites
Address the round-5 review findings on the navigation-rail change, plus
the residual direction of the round-4 fold-band fix:
- The Cmd/Ctrl+B toggle computed from the effective folded state is a
constant expansion in the split-view fold band; it now rewrites the
unchanged preference there instead of flipping a stored `true` to
`false` on a keypress that changes nothing on screen.
- The rail's column restore fires `onCollapsedChange(false)` only when
the sidebar is actually collapsed, so a drawer tap on Channels/Live no
longer silently clears the desktop preference from the forced-open
drawer.
- The Channels rail entry restores the column only when the Channels
column exists (`channelNavigationEnabled`), matching openNavigation's
rule for full-page entries.
- The App's rail-Home callback drops its redundant preference write; the
sidebar's openNavigation already owns column restoration.
- A drag released at or past the container cap while the cap already
bound the start width no longer persists the capped width over a wider
stored preference, and releasing at the drag start restores the
pre-drag state width as well.
- The Live settings settle effect now runs only on a genuine status
change, so typing a value that briefly trims onto the saved one no
longer loses the just-typed characters.
Each behaviour is pinned by a unit case that goes red without its fix.
* fix(web-shell): settle Live drafts only on genuinely changed values
Follow-up to the round-5 settle fix: the daemon SDK returns a fresh
status object per fetch, so keying the settle effect on object identity
still let a 1s install poll or focus refresh with unchanged data
converge a padded draft the user was typing. Compare the settle-relevant
saved values against the previous status instead: only a refresh that
actually moved a saved value settles, while draft-only re-renders and
unchanged polls leave the in-progress text alone. Pinned by `does not
settle a padded draft when a poll returns unchanged values`, red against
the identity comparison and green with value comparison; the convergence
cases stay green.
* fix(web-shell): keep capped drags window-scoped and drop detached rail hints
Address the two round-6 Critical findings:
- The container-cap guard added last round only covered a release past the
cap. While the cap binds, the rendered edge cannot follow the pointer, so
the rect-based delta is container-scoped in both directions: a 2px
leftward jitter stored 298 over a 420 preference, and a later host
widening rendered the narrow value. Map the pointer delta onto the state
width whenever the cap bound at drag start, so persistence stays
window-scoped (418), while the past-cap branch keeps priority and an
uncapped drag still persists what the user saw.
- navigationHint held a live rail button and was only cleared by DOM
handlers on the nav. Removed elements fire neither pointerout nor blur,
so an unmounted rail - or just an unmounted hinted button, when its
feature gate flips - left the tooltip Popover open against a detached
node, painting the previous icon label near the viewport origin when the
rail returned. Clear the hint when the rail is gone or its anchor is
detached; the check runs every render because a dependency list cannot
observe a child unmounting while both deps stay unchanged.
Both are pinned by cases that go red without their fix: a leftward jitter
stores 418 (298 without the mapping, with the two neighbouring width cases
still green), and a rail tooltip does not survive a rail -> single -> rail
round trip.
* fix(web-shell): re-baseline hidden-surface and failed-save trackers
Address the two round-7 Critical findings:
- The secondary-workspace completion tracker only re-baselined while the
status surface was hidden, leaving a frozen running snapshot across a
visible Home period. Comparing the next refetch against it repainted a
completion the user watched happen as an unread one. The visible path now
nulls the per-source baseline, so the first hidden cycle after a visible
period is baseline-only: only completions actually observed while hidden
produce a dot. The always-hidden sibling path (and its existing test in
WebShellSidebar.collapse-persist.test.tsx) is untouched.
- A save rejected after the daemon persisted some of its writes (its
settings writes land before setEnabled, which rolls back only `enabled`)
was reported as settings.liveSetup.conflict: the stale apiKey baseline
compared against the refreshed keyConfigured/storedKey flipped by the
card's own request. The rejected save now queues a re-baseline of the
request keys and refreshes, and conflict with those keys is suspended
until the refreshed status lands and re-anchors them (state, not a ref,
so the re-baseline provokes the render that clears it).
Both pinned by cases that go red without the fix: no completed badge after
a plugins -> home -> plugins round trip while the sibling always-hidden
case stays green, and no conflict plus a retryable Save after a partially
landed save.
* fix(web-shell): stop certifying unfreshed secondary workspace status
Address the round-8 Critical (body-level, independently confirmed by
qqqys): the secondary catalog page is loaded and polled only while the
Home column is hidden, but the rail Home button folded its retained
snapshots into the indicator unconditionally. An approval resolved
elsewhere kept painting the amber dot and the "Waiting for approval"
accessible name until the user hid the Home column again — two render
sites for one stale certification, and a regression over the pre-PR
surface, which could only render the dot from a live, fetchable path.
build the rail indicator from the primary page plus only those secondary
sessions whose queries are currently armed. While the Home column is
visible the secondary contribution is dropped entirely; hiding the Home
column re-arms the queries and the still-waiting state comes back. On
live-state daemons the secondary catalog path is unloaded regardless, so
nothing live is hidden by this gate. Pinned by a plugins -> home ->
plugins round trip asserting both the dot attribute and the accessible
name, red without the gate while the always-hidden attention case stays
green.
* fix(web-shell): gate the completion tracker baseline on page freshness
Address round-9 Critical R9-1: nulling the secondary baseline on the
visible path closes only half the leak. On re-entering the hidden
surface the tracker installed the store's retained, never-refetched
catalog page as the new baseline (the guard rejected only loading and
error, never a stale page), so the poll arriving seconds later was
diffed against a snapshot taken before the visible window and a
completion the user watched was repainted as unread — exactly what the
visible-path null was supposed to prevent.
Stamp the moment the surface becomes hidden and refuse to install or
compare any secondary snapshot that is not provably fresh
(`(snapshot.updatedAt ?? 0) < hiddenSince`, an unstamped page counting
as not-fresh). The first after-hidden poll then installs the baseline
from truly fresh data. The strengthened round-trip case drives retained
running -> stale re-entry -> fresh idle; it is red without the gate
(the completion badge and the Finished accessible name both appear) and
green with it, while the always-hidden completion case stays green.
* fix(web-shell): clamp every collapse entrance and clear More on rail teardown
Address round-9 Criticals R9-2 and R9-3 (body-level):
- The split-view fold-band clamp lived only at the Cmd+B entrance; the
rail's Collapse/Expand button reached handleSidebarCollapsedChange
unclamped, so a click that visibly does nothing still overwrote the
stored preference and a stored `true` could never be re-written inside
the band. The clamp now lives once in the shared writer: in the fold
band it re-stores the current raw preference regardless of which value
was requested. (`sidebarCollapsed` and `splitFoldedSidebar` reach the
stable []-callback through refs — the former would close over the
mount-time value and the latter is declared later, TDZ.) The Cmd+B
site shrinks back to passing the toggle intent. Pinned by a third arm
in the fold-band App test that clicks the collapse toggle at 1100px
and finds the stored `true` untouched — red with the clamp removed.
- The More reset effect did not watch layout, so a rail -> single ->
rail round trip left moreOpen=true against an unmounted owner and the
rail returned with a phantom open menu (content in the document,
focus inside, never reopened). `layout` joins the dependency list and
a round-trip case in WebShellSidebar.collapse-persist.test.tsx goes
red without it.
* fix(web-shell): let exit-split navigation restore the column under the fold clamp
Address maintainer P2 inline: after the shared-writer clamp landed, a
Home click from the split-view fold band was still intercepted — the
rail's restoresColumn `onCollapsedChange(false)` wrote the folded raw
value back (correct for taps that stay in split view), and `onOpenHome`
then closed split view with the raw preference unchanged, leaving the
Home column folded until a second click.
Split the intent at the shared writer: `exitFoldBand` marks entrances
that reorganize the layout away from split view, bypassing preservation,
and the rail Home handler uses it when the fold band was the overriding
state at click time. Taps that stay in the fold (Cmd/Ctrl+B, the rail
Collapse/Expand button) keep their preference protection untouched, and
the write still never happens inside the mobile drawer, where
splitFoldedSidebar is forced false by construction — so the drawer case
is unaffected (drawer protection is preserved).
Pinned by a fourth arm in `does not invert the stored collapse
preference in the split-view fold band`: stored `true` at 1100px split
view, click `[data-testid="open-home"]` once, and the column is expanded
with the stored key `false`; dropping the bypass inverts that arm while
the three protected arms stay green.
* fix(web-shell): cover Channels and Live in the exit-split fold exception
Address maintainer P2 follow-up: the exitFoldBand carve-out applied only
to Home, so Channels and Live — whose `openPanel` call exits split view
through the web-shell page set (`setMainView('chat')`) the same way —
still had their restoresColumn write clamped when the fold band owned
the sidebar, leaving the section column folded until a second rail tap.
Extent the same bypass to those two entrances, with each entry's own
column-existing gate: Channels applies it only when channelSidebarEnabled
would actually produce a column (mirroring the rail's
channelNavigationEnabled rule, which still leaves the full-page channels
variant folded), and the Live handler inherits the liveSidebarEnabled
guard it's already defined under.
Pinned by two parallel cases beside the fold-band keyboard/click arms:
stored `true` at 1100px split view → one rail tap on `[data-testid=
"open-channels"]` (with `session_source_metadata`) and one on
`[data-testid="open-live"]` (with showLive) — each expands the column and
writes `false`; dropping both bypasses turns both cases red while the
protected Home arm stays green.
---------
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Shaojin Wen <szujobs@gmail.com>
|
||
|
|
fde56a8203
|
fix(serve): let Web Shell trust a workspace without a terminal (#13146)
* fix(serve): let Web Shell trust a workspace without a terminal Folder trust fails closed: with `security.folderTrust.enabled`, a folder that no rule decides is untrusted, and the only way to record a decision is the terminal prompt or `/permissions`. When `trustedFolders.json` is missing or unreadable, every registered workspace resolves untrusted at once and a Web Shell or Desktop client has no way back in. The existing `POST /workspace/trust/request` only publishes a request for a local operator, which those clients cannot satisfy. Add an operator-authority route that records the decision itself: POST /workspace/trust/grant POST /workspaces/:workspace/trust/grant _qwen/workspace/trust/grant It reuses the strict mutation gate and the existing trusted-folder writer, always targets the workspace the request already resolved to, and returns the status the workspace reports after the write. Refusals mirror the request route: folder trust disabled, managed scratch, live conversation, inactive runtime, unreadable trust file. Grant only; there is no revoke counterpart. The route is registered unconditionally and advertised through a new `workspace_trust_grant` capability that reuses the trust hot-reload toggle, so the Web Shell offers the action only where the decision reaches the running runtime without a restart, and an older daemon shows no affordance at all. Fixes #13130 * test(serve): add workspace_trust_grant to the integration capability baseline The route registration added workspace_trust_grant to EXPECTED_REGISTERED_FEATURES in serve/server.test.ts but not to the second baseline list in integration-tests/cli/qwen-serve-routes.test.ts, which deep-equals the live server's list and therefore failed deterministically. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): document workspace_trust_grant in the protocol table and route envelope The new conditional tag was added to CONDITIONAL_SERVE_FEATURES but not to docs/developers/qwen-serve-protocol.md nor to the advertised-feature list in integration-tests/cli/qwen-serve-routes.test.ts, so capabilities-docs-contract and the capabilities envelope assertion both failed. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-conflict/jmupfte0zk0 * fix(serve): admit SSH trust/grant passthrough and pin grant guards - Add trust/grant to the REST SSH boundary POST passthrough so the workspace-qualified grant route matches its ACP SSH_METHODS twin; without it the Web Shell Trust button on an SSH workspace could only ever answer 501 (R1-2). - Reword the workspace_trust_grant capability row: the tag means the running runtime hot-applies the decision, not that the grant routes exist — they are registered unconditionally (R1-1). - Pin both grant routes' 503 workspace_runtime_unavailable guards with a transitioning primary and a draining secondary (R1-10). - Make the ACP grant dispatch test's pre-write status untrusted so the asserted reply can only be the post-write grant result (R1-9). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmupzgp080a * docs(serve): narrow three workspace-trust claims to shipped behavior Review round 4 of #13146, doc-accuracy only. Three sentences this PR introduced stated something the code does not do: - qwen-serve-protocol.md: the workspace_qualified_rest_core rule said a capability may only define a narrower *read-only* policy, but this PR adds a grant mutation accepted for a registered untrusted workspace. Name workspace_trust_grant as the exception. - web-shell-workspace-trust-grant design doc (EN + zh-CN): the Refusals row promised 500 trusted_folders_invalid for a malformed, unreadable or non-regular trust file. A dangling symlink reads as no file and is replaced with a 200, corruption the write itself discovers answers internal_error, and the load verdict is cached with no production reset. Note all three; the writer is unchanged here. - trusted-folders.md: a missing file leaves every folder untrusted, but a malformed or unreadable one is a fatal configuration error and the trust routes answer 500. Split the two cases and scope the Web Shell recovery sentence to the no-rule case. No code changes. Code-side repairs for the divergences above are tracked separately. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmuq3r0k30o * docs(serve): correct three workspace-trust claims to measured behavior Design doc (en + zh-CN twin): - The "Already trusted" refusal row now names its precondition. A successful grant starts a rebuild that moves the entry to `transitioning`, and while it is there both grant routes answer 503 `workspace_runtime_unavailable` with `Retry-After: 1` even though the trust record is already written, so the row's unconditional idempotent 200 did not hold for that window. - The write-discovered corruption sentence was inverted. A symlinked or otherwise non-regular trust file, a document whose root is not a JSON object, and a rule carrying an invalid trust level all raise `FatalConfigError` from the writer, which `sendTrustError` maps to 500 `trusted_folders_invalid`. Only a syntactically invalid document that was still valid when the daemon first cached it reaches the write as a plain parse failure and answers 500 `internal_error`. Protocol doc: - The plural grant mutation's acceptance is not conditional on the `workspace_trust_grant` tag. Both grant routes are registered unconditionally and neither handler reads the capability, and the tag row already states the routes are served regardless. The tag reports hot-reload wiring only. Docs only: no production code, route, or capability surface changes. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmuqkwas11i * docs(serve): scope the trust-grant guarantees to shipped behavior - qwen-serve-protocol.md: the workspace_trust_grant row no longer states live application unconditionally — a higher-precedence source (IDE trust for the daemon's own cwd, a settings/trust-file load error) still wins, and the response's effective.source names the winner; the tag-absent case now says no writer applies live without hot-reload wiring (the terminal prompt included) instead of pointing at it as a remedy (R2-3). - trusted-folders.md: the malformed-file remedy gains the daemon-restart step (a running daemon caches the failed load and keeps refusing the Trust action), the fail-closed claim is scoped to folders no higher-priority signal decides, and the 500 claim is narrowed to the v1 routes with the v2 status route's 200 + configured.state:"error" shape named (R1-15). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): fail a trust grant that does not take effect grantWorkspaceTrust answered 200 on the write alone, never checking that the decision it recorded is the one the trust evaluator reaches. With an equal-depth DO_NOT_TRUST rule under an alias spelling of the same directory, a settings error, or an IDE distrust of the daemon's own cwd, the new TRUST_FOLDER entry loses and the workspace stays untrusted — the panel spins to its 30s deadline and silently reverts. After the write, re-evaluate with the same evaluator the reconciler drives (reading the post-write in-memory rule set, so no disk round-trip), and answer 409 trust_grant_ineffective when the grant did not take. Refusals tables updated in both design-doc languages. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(serve): scope the trust-grant ACP and 503 claims to shipped behavior - Endpoints: state the Qualified row's ACP precondition. An untrusted non-primary workspace is refused 403 untrusted_workspace at the mount resolver before any dispatcher exists, so for the locked-out secondary this design exists to recover, the ACP column is undispatchable and recovery is REST-only. - Advertising: the consistency claim covers the capability tag and the REST surface, not the ACP counterpart. - Refusals: the 503 during a rebuild is retryable-until-applied, but the reference panel reports every rejection identically (#13186). And re-keying a path only replaces an equal-depth rule under that spelling, so an alias-spelled DO_NOT_TRUST survives the write and the grant answers 409 trust_grant_ineffective. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmuqvm3p20y * test(serve): pin SSH admission of workspace/trust/grant The SSH capabilities test swept the already-filtered advertised list and asserted only arrayContaining membership plus the absence of an ssh_workspace_operation_unsupported frame, so every assertion stayed true when the list shrank. Dropping 'workspace/trust/grant' from SSH_METHODS left the suite green while SSH-backed workspaces lost the only in-app trust recovery the design doc promises. Assert membership of '_qwen/workspace/trust/grant' in the advertised array. The sweep loop is unchanged: it still drives each method through Promise.allSettled against a dispatcher with no workspace service. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmuqvm3p20y * fix(serve): reply to a trust grant whose own write retired the generation The ACP `_qwen/workspace/trust/grant` case re-asserted the captured generation guard after the grant. A successful grant notifies the trust monitor, whose reconcile calls `registry.beginReplacement` and closes that very guard, so an operation that had already written and applied the decision was answered with `-32603` / `workspace_runtime_unavailable` / 503 while the REST twin answered 200. Keep the pre-write assert: for this method the generation closing is the success signal. The dispatch test now closes a real guard inside the spied grant, so re-adding the post-write assert turns it red. Also bring two documents back to the implemented contract: the `workspace_trust_grant` protocol row and the Web Shell bullet in trusted-folders.md. A refused grant answers 409 with only `error` and `code: "trust_grant_ineffective"` and carries no `effective` member, three causes can defeat a grant rather than two, the Trust action keys on the advertised capability rather than on route presence, and on a daemon without hot reload the v1 trust status does report the recorded decision while the runtime gates wait for a restart. Drop the never-read `state`/`source` fields from `WorkspaceTrustGrantIneffectiveError`. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmurpmcje1k * test(serve): forward the request context in the trust-grant dispatch spy The new `_qwen/workspace/trust/grant` dispatch test bound `fakeWorkspace.grantWorkspaceTrust` and re-invoked it with no arguments. `fakeWorkspace` is closed with `as unknown as DaemonWorkspaceService`, so the bound reference carries the interface arity `grantWorkspaceTrust(ctx: WorkspaceRequestContext)`, and `tsc --build` failed with TS2554. That runs inside the `prepare` hook of `pnpm install`, so the single type error took down every job at its Install dependencies step. Forward the context the dispatcher already supplies so the mock keeps calling the real fake with the same argument. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-conflict/jmurtjt4g1q * fix(serve): give the ACP trust-grant refusal a branchable 409 frame `WorkspaceTrustGrantIneffectiveError` had no arm in `toRpcError`, so the name-keyed switch fell through to the default arm and ACP clients received `{code: -32603, message: 'Internal error', data: {errorKind: 'internal'}}` for a refusal the REST twin answers as `409 { code: 'trust_grant_ineffective' }`. SDK transport negotiation prefers acp-ws/acp-http over REST, so that opaque frame was the default client path: a client could not tell a durably recorded but outruled grant from a daemon fault, and could not stop retrying. Map it beside the other typed refusals, following the shape the file already uses for a 4xx-classified conflict (`data.httpStatus` carries the 409, the JSON-RPC code agrees with the REST classification). The message is the same client-safe string REST already ships, so nothing new is disclosed. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmus0c5f620 * fix(daemon): Verify trust grants against persisted policy * fix(serve): preserve effective workspace trust grants --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com> |
||
|
|
5374641323
|
feat(memory): Opt-in selector skip for a unique new recall hit (#13158)
* feat(memory): opt-in extraction cooldown and selector skip experiments Moved out of #13020, where they had been bundled with unrelated deferred-tool work. Content is the #13020 head ( |
||
|
|
576689d073
|
fix(channels): clean up owned workers after config loss (#11071)
Some checks failed
SDK Python / Classify PR (push) Has been cancelled
SDK Python / SDK Python (3.12) (push) Has been cancelled
SDK Python / SDK Python (3.10) (push) Has been cancelled
SDK Python / SDK Python (3.11) (push) Has been cancelled
Qwen Code CI / Desktop Shell (windows-2022) (push) Blocked by required conditions
E2E Tests / Build for E2E (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 1/2 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 2/2 (push) Blocked by required conditions
E2E Tests / E2E Interactive - OpenTUI renderer (bun) (push) Blocked by required conditions
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Flyway migration version uniqueness (push) Waiting to run
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
Android Mobile Shell / Build, unit tests, and lint (push) Waiting to run
Android Mobile Shell / Keystore and profiles (API 26) (push) Waiting to run
Android Mobile Shell / Keystore and profiles (API 36) (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / Runtime Broker and Managed Agent MariaDB / Java 21 (push) Waiting to run
Verify Landlock helper / Native Windows settings and streams (push) Waiting to run
SDK Java / Hosted process fault gates / MySQL 8.4 / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
Verify Landlock helper / Native Linux CLI boundary (push) Waiting to run
Verify Landlock helper / arm64 (push) Waiting to run
Verify Landlock helper / x64 (push) Waiting to run
* fix(channels): clean up owned workers after config loss Addresses #11063 for a single missing-config owner. Preserve revision and runtime ownership checks and make absent settings removal idempotent. * fix(channels): clarify missing-config ownership diagnostics Address PR #11071 review with named ownership checks, distinct rejection diagnostics, and explicit passive-read assertions. Preserve the current deletion contract. * fix(channels): gate missing-config delete convergence on persisted startup state * fix(channels): harden missing-config delete convergence - Re-read the merged channel config view after the worker stop window and fail closed with channel_settings_conflict when a configuration reappears at another scope mid-delete: the optimistic-concurrency token covers only the workspace's own scope files. - Reject the convergence while the worker manager is mid-transition: a starting or reconciling manager reports the candidate selection as no workers and nothing committed, which reads as the silent shape without being it. - Gate the "another scope" refusal on a record-valued merged entry so a legacy scalar entry in the workspace's own file converges instead of being misattributed to user or system settings. - Pass pre-loaded settings with skipLoadEnvironment to the injected loadChannelsConfig so the gate read no longer writes the target workspace's .env into the daemon's process-global environment. - Deduplicate the ownership invariant on the missing-config path: assertConvergeableRuntimeOwner now returns the confirmed owned-worker outcome instead of remove re-deriving it through a provably dead gate. - Correct both doc pages: the channel_instance_not_found promise only holds when the selected workspace's resolved scope does not own the configuration, and a home-directory workspace resolves that scope to the shared user file. * fix(channels): reject configured channel delete while runtime is mid-transition The mid-transition guard lived only on the missing-config branch, so a DELETE of a configured channel landing during the first-start window — when committedChannelNames() still reports nothing — skipped both the ownership check and the worker stop, deleted the configuration, and answered 200 with runtime stopped while a worker for that name was coming up. Hoist the settled check above the branch split so both branches inherit it, and pin the configured shape with a test. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): narrow mid-transition channel delete guard to at-risk channels (#11071) --------- 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[bot]@users.noreply.github.com> Co-authored-by: qwen-code[bot] <qwen-code[bot]@users.noreply.github.com> |
||
|
|
612a552959
|
feat(channels): add IMAP and SMTP email channel (#12939)
* feat(channels): add IMAP and SMTP email channel * test(channels): include email in managed channel catalog expectations --------- Co-authored-by: destire-mio <248462155+destire-mio@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
45ee202cb1
|
feat(agents): add isolated remote Qwen runtime hosts (#12582)
* feat(serve): join a coordinator as a runtime with one link `qwen serve --join <link>` takes the single link the coordinator shows under Agent → Runtime → Add runtime (<coordinator>/join/<workspace>/<token>) instead of --agent-host-server, --agent-host-workspace-id and the QWEN_AGENT_HOST_ENROLLMENT_TOKEN variable typed separately. The token is single-use and short-lived; the host saves its own credential after enrolling, as before. A host now advertises every program it can run (Qwen Code always, Codex when its CLI is on PATH or was asked for) so one machine appears as one runtime offering several programs. * feat(web-shell): Agent page tabs and a one-line Add runtime dialog The Agent page now always shows its three tabs (Agents, Conversations, Runtimes); they were hidden whenever the page was opened from the sidebar, which instead carried three sub-links. The sidebar keeps a single Agent entry. Add runtime is a dialog: it issues a single-use join token, shows `qwen serve ... --join '<link>'` (and an npx variant) with copy buttons, warns when the address is localhost or plain HTTP, and waits — the live stream flips it to "<name> is connected", listing the programs that machine offers, with a shortcut to create an agent on it. The old inline enrollment and remote-connect forms are gone; connecting an existing serve by address and token is the dialog's second tab. The client no longer builds the long enrollment command itself. Removes web code the PR audit confirmed dead: ThreadConversations.tsx, summariseWorkspaceWork, ThreadGroup.label, the unreachable create-method chooser in AgentCreatePage, the initialCreateTask/api/runtime/loading props, unused CSS module classes and a duplicated prop spread. * refactor(agents): remove dead workspace-agent code confirmed by audit Drops the scripts/audit harness (not wired into CI), request capture, the Codex turn-result adapter, the notification pipeline nothing could trigger, unused store/dispatcher/lease/A2A exports and legacy background-agent plumbing. Recover only reports work when a parent report is actually pending. * fix(web-shell): hide agentCollaboration from the settings message The experimental flag has no alias in the settings dialog map, which the alias-drift test rejects. * feat(agents): bind agents to a runtime program and share them over A2A An agent on a runtime can pick the program to run (Qwen Code or Codex) when that runtime offers both; the host runs each assignment with the program its agent is bound to and falls back to its default otherwise. Agents can be shared with callers outside the workspace: each share is its own A2A grant with a 7-day expiry, the token is shown once with a ready-to-run SendMessage curl, and any share can be revoked. A failed run's retry row now hides while that agent has a live run and once the conversation is closed. * fix(agents): push runtime liveness changes, not every heartbeat Heartbeats rewrite hosts.json every five seconds, and each write made every open tab refetch the whole roster and thread list. A runtime that died wrote nothing, so it never showed as offline without a reload. The event stream now compares what a person can see (runtimes, their programs, online or offline) on each hosts.json write and on a five-second timer, and only announces a change. Add runtime also recognises a machine that rejoins under its saved identity. * feat(web-shell): polish Agent page, New agent and the @ picker - New agent needs only a name; it can start from a .qwen/agents role, preselects the runtime it was opened for, and shows both programs with the reason one is unavailable. - Agent rows read "Program · Runtime", offer "@ Mention" and fold configure/share/pause/retire into a menu; moving an agent to another runtime keeps its program. - The @ picker shows each agent's program and runtime and ends with "New agent…"; at-items can now be actions via onSelect. - The collaboration composer says who a reply reaches and that other members are not interrupted; skip reasons and leftover strings are localised. * feat(agents): show a running agent's tool calls as steps A running member now lists its latest tool calls under its status line, one per line with running, done or failed, like a CI job's steps. The turn stream reports updates as an object so steps travel with stage and text; remote runtimes still report stage and text only. Also: subtasks in the team panel name their assignee, join links last 15 minutes as the spec says, and a share created from a localhost page warns that its address only works on this computer. * fix(agents): skip a liveness check when the store is busy A failed hosts.json read looked like every runtime leaving, which announced two spurious changes. * feat(agents): let a person's post pass the token budget The tree-wide token cap now binds only agent-authored posts, like the turn budget: agents on an exhausted tree stop waking each other, while a reply from a person still books work. Every automated path posts as an agent or the system, and A2A intake always starts a new root thread, so nothing unattended gains a bypass. * refactor(agents): move cross-runtime and A2A to a follow-up PR This PR now carries local collaboration only: agents on this computer, shared threads, streaming, waiting states, the @ picker and the Agent page with Agents and Conversations tabs. Moved out, to return in a stacked PR: agent hosts (join links, the --join and --agent-host-* flags, enrollment, heartbeat, lease and pickup routes, the host client and Codex host sessions), per-agent runtime and program binding, the Runtimes tab and Add runtime dialog, the A2A server, grants, external intake and agent sharing, and the @a2a-js/sdk dependency. Records written by builds that had them keep loading; the extra fields are ignored. * feat(agents): run agents on other computers and share them over A2A Brings back everything #11206 moved out, stacked on it: agent hosts (join links, the --join and --agent-host-* flags, enrollment, heartbeat, lease and pickup routes, the host client and Codex host sessions), per-agent runtime and program binding, runtime liveness events, the Runtimes tab and Add runtime dialog, the A2A server, grants, external intake, agent sharing and the @a2a-js/sdk dependency. This reverts commit |
||
|
|
b906f937ec
|
fix(serve): upload session attachments in 512 KiB chunks (#12923)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Lint & Static (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / web-shell E2E Smoke (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (no-AK, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (ubuntu-22.04) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (windows-2022) (push) Blocked by required conditions
E2E Tests / Build for E2E (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 1/2 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 2/2 (push) Blocked by required conditions
E2E Tests / E2E Interactive - OpenTUI renderer (bun) (push) Blocked by required conditions
E2E Tests / channel-plugin E2E (nightly) (push) Blocked by required conditions
E2E Tests / cron-interactive E2E (nightly) (push) Blocked by required conditions
E2E Tests / web-shell Browser Regression (push) Waiting to run
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
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Runtime Broker and Managed Agent MariaDB / Java 21 (push) Waiting to run
SDK Java / Hosted process fault gates / MySQL 8.4 / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* fix(serve): upload session attachments in chunks * codex: address PR review feedback (#12923) * codex: address PR review feedback (#12923) * codex: address PR review feedback (#12923) --------- Co-authored-by: jinye.djy <3007091+doudouOUC@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
8c914ebe03
|
feat(agents): add local workspace-agent collaboration (#11206)
* test(agents): kill a writer mid-transaction, and say what that does not prove A daemon can die holding the workspace lock. Two things must be true afterwards and now are, from a real SIGKILL rather than an argument: the store stays readable with nothing written before the crash lost, and writes come back without anyone clearing the lock — a lock held by a dead process that nothing reclaims is a workspace wedged forever. How long recovery takes is reported rather than asserted, and that is a correction. An early probe measured a 10.5-second outage and I wrote that into both the script and a code comment as a measured fact. Four repeat runs recover in about 25ms and the outlier never reproduced, so the assertion that a write in the gap is refused was dropped: it depends on where in the staleness cycle the crash lands, and stating it as an invariant was wrong. Both comments now say what I can actually support. The route maps that refusal to 503 with a Retry-After instead of a 500 quoting a lock file at whoever clicked. It is a wait, not a fault, whether it lasts 25ms or ten seconds. The script also records what it does not cover, because I checked: replacing atomicWriteJSON with a plain truncating write leaves this and the concurrency script green. The file lock means two writers never touch one file at once, so atomicity only matters for a crash *during* a write, which killing an idle holder never reaches. store.test.ts covers that with fault injection. A script that passes under a mutation which should break it does not cover that property, and saying so is the difference between coverage and the appearance of it. * test(agents): fuzz the store against its invariants The other scripts check scenarios someone thought of. This one draws operations at random — create, post as a person or an agent, claim, finish, toggle, retire — and after every step asserts what must hold regardless of order: every thread readable, message sequences unique and increasing with the watermark ahead, run ids unique, queueSequence unique across the workspace, parent and root ids resolving, terminal runs carrying an end time, no agent running more than its maxConcurrentRuns, and no run booked for an agent after it retired. The status resolver is called on every thread, since it has to answer for any shape the store can reach. A refusal is a legal outcome and is recorded as one — the store simply has to stay consistent through it. The seed is printed and accepted back, so a failure is reproducible instead of a story about a run nobody can repeat. Clean over seven seeds, up to 800 steps. What makes that worth anything is what it does when the invariants are broken. Removing the concurrency check from claimRun is caught by step 42 on one seed and step 20 on another, named as "ag_1: 2 running over a limit of 1". Removing the retirement guard from decideDispatch — the exact bug fixed in |
||
|
|
a765229c0a
|
fix(daemon): persist session approval mode across cold restore (#12918)
* fix(daemon): persist session approval mode across cold restore * fix(core): Skip approval notifications for derived configs Prevent child-local approval mode changes from invoking the root-only listener on derived Config overlays. Clarify when explicit mode changes create a transcript and how recorder failures are retried. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
e39567eb56
|
fix(cli): honor --bare and --safe-mode in the cross-session messaging gate (#9845)
* feat(core): accept cross-session messages behind an inbound gate Step two of QwenLM/qwen-code#8724, rebuilt on current main now that the registry from step one has landed. A session can be reached by another session on the same machine, and every message that arrives is gated before the model can act on it. Off by default behind `agents.crossSessionMessaging`. Transport is one UNIX domain socket per session, NDJSON over the wire, one frame per line. The socket directory is 0700 and the socket 0600, and that is the whole access-control story: Node cannot read SO_PEERCRED without a native addon, so a frame's claimed `from` is not authenticated. Everything downstream assumes that. The gate is why the transport and the policy land together. With an explicit `agents.crossSessionInbound` the user decides; unset, the policy follows approval-mode parity — a message auto-delivers only when acting on it cannot do more than the sender could already have done itself. Anything unreadable holds. Held messages are settled rather than stranded: the buffer is bounded, shutdown expires the rest, and every terminal outcome goes back to the sender as a control frame. Content reaches the model inside a <cross_session_message> envelope with the delimiter defanged in the body, so a peer cannot close the envelope early and forge one attributed to the user. The envelope carries a fixed notice that a peer holds none of the user's authority, and the auto-mode classifier gains the matching rule. `/peers` lists and releases held messages; without it, holding would be indistinguishable from dropping. Landed only after four independent reviews, whose non-obvious findings are worth naming because they were all live defects: - A receiver in AUTO_EDIT auto-accepted peer messages and nothing reviewed what they caused. The old rationale — "every consequential action still faces its own gate" — is true of AUTO but false of AUTO_EDIT, where edit confirmations are approved outright and the classifier does not run. A peer could get a file written with no prompt, no classifier and no user. Receiver policy now turns on whether the mode reviews actions at all, not on whether it is YOLO. - `server.unref()` does not cover accepted connections, so any peer that connected and lingered pinned the session open forever. - `fs.mkdir(recursive)` and `fs.chmod` both follow a symlink, so another user could pre-create the world-writable `/tmp` fallback directory and redirect our 0700 chmod onto a directory of ours. - A full listen backlog surfaces as EAGAIN on Linux, not EBUSY; the busy case was being read as dead on the primary platform. - The envelope was escapable without markup: `escapeAttribute` handled `&<>"` but not newlines, so a crafted `fromName` could emit free-standing lines inside the opening tag. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(i18n): translate the /peers description in strict-parity locales `Test (ubuntu-latest, Node 22.x)` has failed every run on this branch with FAIL src/i18n/mustTranslateKeys.test.ts > does not fall back to English for any built-in command description in strict-parity locale { code: 'zh-TW' | 'zh' } AssertionError: expected [ 'peers' ] to deeply equal [] `peersCommand` set `description` to a literal English string rather than routing it through `t()`, so both strict-parity Chinese locales fell back to English and the parity test — which exists to catch exactly this — reported the `peers` path. Four merges of current main were attempted against this check. None could fix it: the untranslated description is introduced by this branch, so no state of main contains the missing keys. Use the `get description() { return t(...) }` form every other built-in command uses, and add the key to `en.js`, `zh.js` and `zh-TW.js`. All three locale files remain in sync at 1817 keys. Verified both directions in a clean worktree install: with the change, `mustTranslateKeys.test.ts` is 20/20; stashing it reproduces the CI failure exactly, `expected [ 'peers' ] to deeply equal []`, twice. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VgTjRF91xANQh6SY9YGyCf * fix(ipc): close Critical review findings in the cross-session inbox - Dedup held msgIds case-insensitively, matching /peers resolution - Print unique dash-stripped handles and resolve them in /peers - Bound outbound sends: in-flight cap plus an absolute deadline (socket.setTimeout is an idle timer a dribbling peer resets) - Defang whitespace-split and quote-glued envelope delimiter tokens - Seed onHeldChange subscribers with the current held snapshot - Skip POSIX-only socket-path tests on win32 like the sibling suites * fix(cli): gate held-message announcements on the held set growing Once the hold buffer is full, every further peer frame evicts the oldest entry while arriving under a fresh id; announcing each of those appends a history item and re-renders per frame — reintroducing one layer up the unbounded growth the hold buffer's ceiling exists to prevent. * fix(ipc): close round-6 Critical findings in the cross-session inbox * fix(ipc): close round-7 Critical findings in the cross-session inbox - defang envelope delimiters split by render-invisible separators the \s class misses, sharing flattenPeerLabel's strip class so the two cannot drift (R5-1) - restore admission-failed queue submissions deferred until idle, the same recovery the /btw path uses, so a restored peer envelope cannot leak raw into the racing turn's mid-turn steer drain (R7-1) - cap the accepted-message backlog symmetric to the held cap: the pre-wiring buffer and the post-wiring queue refuse once full, with an honest 'expired' receipt instead of unbounded socket-speed growth (R7-11) * fix(ipc): close round-8 Critical findings in the cross-session inbox - Refuse any msgId canonicalizing to `all` at the wire boundary and fold the /peers bulk keyword case, so a peer-chosen id can never alias the bulk action or trap a per-message decision (R8-1, R1-11, R1-27). - Re-hold approved messages whose delivery fails instead of silently dropping them: decide() returns 'failed' and parks the entry back at its position, reevaluate() re-holds failed releases, and /peers reports the failure honestly (R8-2, R1-18). - Drain accepted peer envelopes on a preprocessing-free path: the queue marks them peer, the drain submits them with the Teammate send type (which returns before slash/shell/@ handling), renders the one-line projection, and restores failed admissions as peer entries (R8-3). - Close the envelope-defang class structurally: escape every `<` in peer content so no forged delimiter survives regardless of wedged invisibles or homoglyph spellings; the open-enumeration regex is deleted (R5-1). Every guard is witnessed by new focused tests and verified with mutation probes (each probe fails its witnesses when removed, passes restored). * fix(ipc): close peer messaging lifecycle races * feat(core): address local peer sessions from agent tools * fix(core): make peer addresses unambiguous * fix(core): reserve peer routing namespace * fix(ipc): complete peer delivery feedback * fix(ipc): harden peer session routing * fix(cli): honor effective peer messaging gate * fix(cli): report effective peer messaging state * test(cli): teach the cross-session test doubles the new gate accessor The interactive UI gates the peer inbox on `config.isCrossSessionMessagingEnabled()` instead of the merged `agents.crossSessionMessaging` setting, so every config double that exercises `startInteractiveUI` must expose that accessor. The shared `sessionRegistryConfigStub` in `llm.test.tsx` builds most of those doubles, and the four `startInteractiveUI.test.tsx` cases that assert the inbox binds need the enabled config rather than the default. Fixes the 37 failing cli tests (`Tests 37 failed | 28548 passed`) in the `Test (ubuntu-latest, Node 22.x)` lane; both files now pass 101/101. * refactor(core): drop peer-receipts, superseded by peer-send on main The send-side receipt correlation this module implemented shipped on main as packages/core/src/ipc/peer-send.ts (#10158, step 3 of #8724), in a strictly richer form: it canonicalizes message ids, models seven delivery statuses with explicit legal-transition sets, bounds tracked sends at MAX_TRACKED_SENDS, and reports the settled receipt with its age. This copy had no production import site -- only its own test imported it -- so keeping it could only let it drift from the shipped version. Removing both files leaves the branch as just the cross-session messaging gate change it now describes. No behavior change. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): gate every peer-inbox reader on --bare and --safe-mode The only reader was isCrossSessionMessagingEnabled(settings.merged), which cannot see the two session-level suppressions. A session started with --bare or --safe-mode bound a UDS inbox and published its ipcPath into the live-session registry, advertising itself as addressable from the modes that exist to close cross-process surfaces. On the daemon path it was worse than visible. sendToPeer reports `disabled` only when the caller's own record lacks an ipcPath, so publishing one is also what let a suppressed hosted session send -- while inbound was already refused. An interactive TUI under the same flags bound nothing and sent nothing; a hosted session did both. Answer the question once, in the file whose doc comment already claims to be the one place it is answered: crossSessionMessagingSuppression(config) reads the two accessors Config already carries, and isCrossSessionMessagingActive(settings, config) narrows the setting with it. All four readers now ask that -- the interactive bind, the interactive failure banner, /peers, and registerHostedSession. /peers also stops hardcoding "--bare or --safe-mode" and names the cause that actually fired, with the environment channel that reaches each, so a session suppressed only by QWEN_CODE_SAFE_MODE recognises why. Drops the ConfigParameters field and accessor an earlier shape of this branch added. Config already stores both flags and loadCliConfig already passes them in, so the parameter duplicated two booleans that were on the object handed to every read site. Its fail-closed default was also what made the hosted reader look unsafe to wire, which is why that reader was deferred rather than fixed; reading the accessors removes both the parameter and the reason. Each new case was mutation-verified and each mutation reverted: ignoring config at the ACP gate, at the interactive bind, or at the banner branch turns the corresponding cases red; deleting the /peers message arm turns both of its cases red; dropping the getBareMode clause turns the bare-mode cases red in three suites. Refs #8724 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs: record that --bare and --safe-mode suppress cross-session messaging Five places stated the settings-only rule as a contract and would be wrong after the gate change: the agents.crossSessionMessaging row, the two --safe-mode inventories, the hosted-session registration paragraph, and the SDK's inbox note. The row now carries the same "Ignored in --bare and --safe-mode" clause its sibling general.outputStyle already has, and both inventories list cross-session messaging alongside the other surfaces safe mode closes. The dated design record docs/design/2026-09-02-peer-inbox-reachability.md is left as written -- it describes the state at its own date, and rewriting it would falsify the record rather than update it. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): name both causes when a flag and the setting both turn messaging off `/peers` answered with the startup suppression whenever one was in force, so a session whose own `agents.crossSessionMessaging` was also off was told the setting cannot matter, and was sent to a restart that leaves messaging off with the surviving cause still unnamed. The scope remedy now leads and the flag follows as a second cause when both fire; the single-cause text is byte-identical to before. Also qualify the SDK's universal reachability claim for the two flags in the three copies that carry it (package README, the `endpoint.ts` file JSDoc and the hand-maintained `docs/developers/sdk-typescript.md` mirror, whose inbox bullet was still the pre-PR twin of the one this branch amended), and record at the gate that an *inherited* `QWEN_CODE_SAFE_MODE` — unlike `QWEN_CODE_SIMPLE`, which `spawnChannel.ts` scrubs from spawned `qwen --acp` children — suppresses every session a daemon or editor hosts. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmujyzhfka1 --------- Co-authored-by: qqqys <qys177@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: yiliang114 <effortyliang@gmail.com> |
||
|
|
7f06a1e082
|
docs(developers): document CI and release repository variables (#11129)
* docs(developers): document CI and release repository variables * docs(developers): scope the worker-cap claim and note the retry tradeoff Review feedback: state which Vitest invocations actually consume QWEN_CI_VITEST_MAX_WORKERS, soften the retry-masking absolute, and point at the workflow files as the source of truth for the defaults. * docs(developers): attribute each CI pin to the test that actually holds it Review feedback: release-workflow.test.js does not pin the release worker-cap expressions; package-scripts.test.js asserts the QWEN_CI_VITEST_MAX_WORKERS / ecs-qwen- expression, while release-workflow.test.js pins the release retry and timeout expressions. Name both files with their respective attributions. * test(ci): keep documented variable defaults aligned with workflows * test(ci): derive documented variables bidirectionally and fix web-shell scope note * docs(ci): clarify variable scope and guard documented defaults * fix(ci): format variable guard and clarify worker scope |
||
|
|
5deddb722b
|
docs(tools): sync shell.md with the shipped run_shell_command contract (#12841)
* docs(tools): sync shell.md with the shipped run_shell_command contract Four statements in this file contradict the tool as shipped. is_background was documented as required -- twice in the arguments list and the Usage note, plus a "Required Background Parameter" section whose rationale was backwards. The schema is required: [command] (shell.ts:5762) and the parameter description says "Optional: ... If not specified, defaults to false (foreground execution)" (shell.ts:5743), so foreground is the fallback, not what the parameter exists to avoid. The foreground bullet list recommended cat and grep as one-time foreground commands, which the same tool description forbids in favour of the dedicated tools. #12532 replaced that list in the shipped description with one line; this mirrors it and states why the file tools are not candidates, so the list does not come back. "or by explicitly adding & to commands" offered & as a second control for backgrounding. On the managed background path a bare trailing & is stripped as redundant (stripTrailingBackgroundAmp, shell.ts:143, called at :4125). The sentence is now scoped to that path instead of claiming & never works: the example further down uses it with is_background=false, which is the unmanaged path, and is left alone deliberately. Verified against origin/main at |
||
|
|
81c260bb04
|
fix(mcp): Support larger Apps, scoped tool calls and isolated origins (#12258)
* fix(mcp): Make App resource limits configurable per server * docs(mcp): Explain App resource limits in pool fingerprints * fix(mcp): Degrade oversized App HTML per historical page document (#12258) Address review on the App resource limits change: - WebShell historical-page admission now drops each MCP App display's html whole (never truncated) when a page exceeds its byte budget, so two ceiling-size documents degrade to fallbackText instead of failing the whole page as non-retryable unavailable; the design doc ceiling rationale now names all five budgets a retained document crosses. - Pool fingerprint hashes App resource limits at their enforced (clamped/floored/defaulted) values via a shared module, so configs with byte-identical enforced policy share one pool entry. - Limit warnings name the setting in the source that declares the server (extension manifest or .mcp.json) instead of always pointing at settings.json, whose same-named entry would shadow rather than merge. - Timeout warnings credit the key that produced the deadline (timeout when no explicit App timeout is configured). - A non-finite configured limit now logs a debug warning naming the server, key, rejected value, and fallback instead of being silently discarded. - Add a reconnect-replay test pinning that App resource limits survive rediscovery, and sync the daemon transport-pool doc with the fingerprint contract. * fix(mcp): Preserve live sessions when App HTML exceeds queue budgets * fix(mcp): Preserve App limits across SDK and settings configuration * docs(mcp): Use verified Amplitude resource timeout * fix(mcp): Degrade App HTML in the replay envelope and window (#12258) - Restore the live byte cap's first-item rule for degraded App frames so a caught-up subscriber is admitted, not evicted, when even the reduced frame overflows an empty queue - Refuse to degrade an App frame whose fallbackText is empty; the byte cap eviction keeps the original resumable from the replay ring - Blank replayed App html oldest-first when a restore page exceeds the 32 MiB replay envelope so the session loads with fallback text instead of failing closed with transcript_page_too_large - Degrade retained App html oldest-first in the compacted replay window before evicting segments, so an oversized App turn is neither kept above the window budget nor evicted by the next turn * fix(cli): Show a notice for omitted HTML-only replay apps * fix(mcp): Render third-party Apps with isolated origins and scoped tools * fix(mcp): Align App route telemetry and regression fixtures * fix(mcp): Preserve historical App fallback and document origin isolation * fix(mcp): Harden App sandbox and preserve in-flight rendering * fix(mcp): Preserve vendor origins over remote connections * fix(web-shell): preserve MCP App tools in historical pages * fix(mcp): repair App call recovery and approval capacity * fix(mcp): repair pooled App connections in the calling session * fix(mcp): keep App approvals responsive and repair guarded connections * test(cli): update MCP App telemetry route audit * fix(web-shell): drop the duplicate session id on the pane transcript The main merge left sourceSessionId twice on TranscriptViewport, which fails the web-shell typecheck and stops every job at install. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(mcp): revoke failed apps and clarify resource timeout guidance --------- Co-authored-by: yungsen.hys <yungsen.hys@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
663d98eac5
|
feat(acp-bridge): deliver workspace changes to every paired engine (#12807)
On a paired Legacy/Managed Bridge, permission rules, the settings reload, the Session Workflow gate, model providers and Skills now reach every live engine. After workspace control handles the command, each live channel of another engine gets a versioned qwen/control/workspace/change notification and must acknowledge that exact revision; an engine that exits during delivery counts as settled. An engine that does not acknowledge is quarantined with the new reason workspace_change_unacknowledged, through the paired quarantine: no fresh sessions or new work, settled sessions close, and the channel retires by the drain deadline. After a change that tightens permissions its running turns are cancelled at once, its permission requests are refused, and Goal turns the child reports later are cancelled. A late exact acknowledgement of every missing change ends the quarantine early. Every quarantine now also refuses shell commands and continuations, and checks a prompt again after its attachments resolve. The command fails as partially applied; the workspace service still publishes saved permission rules and returns the Legacy reload result. Propagation is skipped while a workspace stop is in progress, and single-factory Bridges are unchanged. Part of #12737 (slice B2b, part two). |
||
|
|
a480bc7815
|
feat(acp-bridge): give each paired channel its own workspace runtime identity (#12776)
Each channel now keeps the epoch allocated when its handshake completed, so workspace status stamps, the lifecycle epoch, idle candidates and stop snapshots describe their own channel. Starting a Managed channel no longer restamps the Legacy runtime, and a Legacy exit makes its MCP and Skills preparation stale even while Managed stays live. A paired Bridge also reports the lifecycle of the Legacy channel that serves workspace control. The workspace runtime coordinator and the workspace service in all four daemon constructors use it instead of aggregate liveness, so a live Managed channel cannot make a failed Legacy preheat look ready. Single-channel Bridges report the same lifecycle as before. A workspace runtime stop now covers every live channel. The preview and receipt list each channel with its own epoch, the top-level epoch is the newest one so a later channel start stales the confirmation, and the stop waits for every registry release. Channels that an incomplete stop left running return to the idle policy. Part of #12737 (slice B2b, part one). |
||
|
|
1060b9e269
|
fix(serve): stop validating load-only restore fields on resume (#12768)
POST /session/:id/resume parsed and rejected liveReplayMode and compactedReplayMode even though resume never uses them and the published OpenAPI resume schema does not declare them, so a resume carrying an invalid load-only value failed with 400 against its own contract. The accepted body fields for load and resume now live in one runtime-owned list that the restore handler follows (load-only fields are parsed for load only) and the REST documentation contract test verifies the OpenAPI schemas against, replacing the second hardcoded array. The TypeScript SDK splits ResumeSessionRequest out of RestoreSessionRequest so resumeSession no longer exposes the load-only replay fields at the type level; the wire builder keeps dropping them for untyped callers. Closes #12146 |
||
|
|
5df67177df
|
feat(web-shell): prepare updates and restart from sidebar (#12734)
* feat(web-shell): prepare updates and restart from sidebar * test(serve): include daemon update in capability contracts * test: complete update contracts and isolate shell output fixtures * fix(web-shell): preserve task URLs and check update launchers * fix(serve): support older Node declarations in update checks |
||
|
|
879c311092
|
feat(serve): Enable private Hosted Harness no-tool text turns (#12713)
* feat(serve): Add private no-tool Hosted Harness turns * fix(serve): repair hosted turn initialization and history * codex: address PR review feedback (#12713) * codex: address PR review feedback (#12713) * codex: address PR review feedback (#12713) --------- Co-authored-by: jinye.djy <jinye.djy@U-Y37CF7G3-1943.local> Co-authored-by: Shaojin Wen <szujobs@gmail.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
be1a0a1fb0
|
feat(serve): support model and reasoning selection at session creation (#12308)
* feat(serve): support model and reasoning selection at session creation
* fix(serve): allow model-only session startup configuration
* test(serve): include startup capability in registry expectations
* fix(serve): roll back rejected standalone startup
* fix(serve): make definite startup rejections retryable and honest
- Map the ACP internalError wire shape ({code: -32603, data.details})
produced when switchModel refuses an unregistered model to
startup_config_rejected on the startup-config call path, so the
ordinary route answers 422 instead of 500 and the standalone path
takes its definite rollback branch.
- Roll back the persisted recording of a caller-supplied sessionId on
POST /session after a definite startup rejection, so a retry of the
same id is no longer answered with 409 session_id_conflict; uncertain
failures keep the recording.
- Publish auto in the OpenAPI approvalMode enum and pin it against
APPROVAL_MODES; narrow the 422/protocol/design cleanup wording to
match the implemented best-effort semantics; restore the sessionId in
the new rollback warning.
* fix(serve): keep uncertain startup faults uncertain and roll back ACP recordings (#12308)
- session-startup-config: drop the -32603 arm so child-side internal
errors (auth, credential, timeout, transport) stay uncertain instead
of a false definite startup_config_rejected; the ACP child now maps
only its two caller-caused switchModel refusals (unknown model for the
auth type, media-only primary) to invalid params, so definite
rejections ride -32602 alone.
- dispatch: roll back the persisted recording of a caller-supplied
sessionId on the ACP session/new transports after a definite startup
rejection, mirroring the REST route; uncertain outcomes keep it.
- routes/session: warn once when the recording rollback is refused or
fails instead of silently discarding the boolean outcome.
- standalone-session-service: extract the shared rollback sequence into
one helper used by both the startup-config and model_selection_failed
branches.
- openapi: qualify the 422 close/rollback as best-effort and replace the
unconfirmable status-view pointer with the self-certifying retry check.
* fix(serve): name the rejected session in startup rollback and align ACP error codes (#12308)
- SessionStartupConfigError carries an optional sessionId so both
rollback gates (REST route, ACP dispatcher) roll back the rejected
session's recording even when the caller never supplied an id or the
child did not honor it; a daemon-generated id no longer leaves a
listed, resumable phantom after a definite 422.
- A refused ACP recording rollback now logs the same inconclusive
warning the REST route emits instead of dropping the cleanup result.
- startup_config_rejected maps to JSON-RPC -32602 (caller input), with
data.httpStatus keeping the REST-equivalent 422; SDK transports key
on httpStatus, so client behavior is unchanged.
- SDK throws DaemonStartupConfigError (TypeError with code
'invalid_startup_config') for pre-transport startup rejections.
- Docs: modelApplied is documented as spawn-scoped again (an attach
omits it or reports the spawn owner's outcome).
- Tests: daemon-generated-id rollback pins on both transports, a
refused-rollback log pin, a service-boundary invalid-config guard
pin, full reasoning-selection acceptance coverage, whitespace trim
pins, and a real-ModelsConfig classifier coupling test.
* test(sdk): keep the message assertion beside the stable code on startup rejections (#12308)
* fix(serve): fail closed on control-character startup model ids (#12308)
A modelServiceId carrying a newline slipped past parseSessionStartupConfig
and produced a core refusal message the single-line classifier regexes
could not match, so a definite caller-caused rejection degraded to an
internal error and skipped the recording rollback. Reject control
characters at the parse boundary and make isCallerCausedModelRefusal
newline-tolerant; align the SDK and integration-guide modelApplied
presence rule with the spawn-scoped wording the bridge and OpenAPI
already publish.
* docs(sdk): name both transport error-code locations in DaemonStartupConfigError JSDoc (#12308)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
---------
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
|
||
|
|
73aa65a4b4
|
chore(desktop): rename packages/desktop-shell to packages/desktop (#12653)
* chore(desktop): rename packages/desktop-shell to packages/desktop The Electron-era packages/desktop was removed in #9085 and the OpenWork downstream has forked, so the short name is free and the shell suffix is the only thing left of the transition. Rename the package directory and every load-bearing reference: workspace negations (npm + pnpm), the ci.yml crate filter, desktop-release.yml, the isolation guard, lint/ignore configs, scripts tests, CLI workspace-fixture tests, and living docs. User-facing identity is untouched: the crate stays qwen-code-desktop, productName stays "Qwen Code Desktop", the bundle identifier, updater feed, release tag scheme, and the legacy electron-bridge asset names are all unchanged. check-desktop-isolation now guards both path names so the old one cannot re-enter a workspace set. Dated design docs under docs/design/ keep their historical paths deliberately. Refs #8596 * docs(design): follow the rename in the undated notification design notes The web-shell-browser-notification-details pair is a living doc without a date prefix, so it falls under the PR's update rule rather than the historical-record exception. * docs(desktop): stop citing paths the rename invalidated Three prose sites and one CI comment still described the pre-rename world, or contradicted themselves once `packages/desktop` named both the removed Electron app and the living Tauri shell. - ci.yml `desktop_shell`: the comment this branch added credited the Cargo.toml guard with keeping "branches from either side of the rename" green. It does not. A pre-rename head's files sit under `packages/desktop-shell`, which the changed-files filter no longer matches, so the job skips before the guard ever runs. Say that, and say what the guard does cover (a head the filter matched because ci.yml itself changed, with no crate at the new path). - docs/design/web-shell-user-message-links.md: "desktop-shell routing" / "desktop-shell clicks" -> "desktop-host", the wording this branch already chose in LinkifiedText.tsx for the same rule. - docs/design/telemetry-runtime-client-attribution-design.md: repoint the `runtime.rs` citation. It backs the live `QWEN_CODE_DESKTOP=1` claim, and the other document of record for that invariant -- the comment in acp-channel-fallback.ts -- was already repointed by this branch, so the two disagreed. Only the path token changes; the doc's dated findings are untouched. - brand-create.mjs / SKILL.md / src-tauri/src/main.rs: name the historical referent as historical (the Electron `packages/desktop` removed in PR 9085) instead of letting it read as a self-reference. In main.rs the citation `packages/desktop/packages/shared/src/config/storage.ts` now looked like a subpath of the file's own package. Rewritten, not deleted: the `QWEN_DEFAULT_WORKSPACE_DIR` clause next to it is the only in-code documentation of that override and still resolves. Deliberately not swept: `scripts/check-desktop-isolation.js`'s `packages/desktop-shell` tripwire, ci.yml's #8132 history comment, `docs/design/9152-architecture-invariant-classification.md` (names both spellings and is accurate), and the date-prefixed `docs/design/2026-07-31-desktop-web-shell-release.md`. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmug1iw422v * test(scripts): pin the desktop crate path and the isolation guard's coverage Both invariants fail green today, and this rename is the second time the crate directory has moved. `scripts/check-desktop-isolation.js` gained `'packages/desktop'` in `nativePrefixes` -- the only production-logic line in this branch -- and nothing read it. Deleting that line leaves the guard's output and exit code byte-identical, because `isNativeLocation('packages/desktop')` then matches no remaining prefix; a later PR that drops `!packages/desktop` from the root workspace list would re-admit the package with CI green. The `desktop_shell` job names the crate directory in five places (the changed-files filter, the Cargo.toml guard and its `::notice::` text, the rust-cache `workspaces:`, and two `working-directory:` values). Nothing asserted they agree with each other or with a directory that exists, so the next drift reports a skipped job as a passed one. The new suite parses both instead of re-running them, and is superset-shaped on purpose: `nativePrefixes` carries a fourth entry (the `packages/desktop-shell` tripwire) that nothing negates, and an exact-match assertion would invite deleting it. The npm/pnpm mirror of the negation list is already pinned by package-scripts.test.js, so this reads package.json only. Verified by mutation, each applied alone and then restored: reverting the filter alternative or either `working-directory` to `packages/desktop-shell`, dropping `'packages/desktop'` from `nativePrefixes`, and dropping `!packages/desktop` from package.json all turn this suite red. Today no other test does. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmug1iw422v * test(scripts): pin what the desktop-isolation tests actually claim Four review findings on scripts/tests/desktop-isolation.test.js, each verified by mutating the thing it claims to pin, before and after. - The comment above the local `isNativeLocation` copy claimed a change to the script's rule "has to be made here too". Nothing reads the rule back from the producer -- `nativePrefixesFromSource` parses the array literal only -- and narrowing the script's rule to prefix equality leaves the suite green. Describe the copy instead of claiming coupling that does not exist. - `still carries the Cargo.toml existence guard` matched only the guard's condition text. Flip the body's `changed=false` to `changed=true`, or move it after `fi`, and the suite stayed green at 5/5 while the job runs every gated step against a crate that is not there. Assert the assignment sits inside the guard's own `if ... fi` body. - Both on-disk checks keyed on the bare directory, so git-ignored residue under `packages/desktop-shell` (`node_modules`, which survives a branch switch with an empty `git status`) reddened two tests on a tree with no defect and blamed the root workspace set. Key on `package.json`, which is what `packages/*` requires to make a directory a workspace member; a real re-entry still reddens both. - The `startsWith(filterAlternative)` assertion compared a path built from `crateDir` -- `filterAlternative` with one trailing slash stripped -- back against `filterAlternative`, so it could not fail for any input. Dropping the trailing slash from the changed-files filter in ci.yml left it green. Replace it with the property that makes the alternative a path prefix rather than a bare string prefix. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmugedurp3j --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
f1361498ad
|
docs(tools): add Firecrawl to the web search MCP services (#12646)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Lint & Static (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / web-shell E2E Smoke (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (no-AK, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (ubuntu-22.04) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (windows-2022) (push) Blocked by required conditions
E2E Tests / Build for E2E (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 1/2 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 2/2 (push) Blocked by required conditions
E2E Tests / E2E Interactive - OpenTUI renderer (bun) (push) Blocked by required conditions
E2E Tests / channel-plugin E2E (nightly) (push) Blocked by required conditions
E2E Tests / cron-interactive E2E (nightly) (push) Blocked by required conditions
E2E Tests / web-shell Browser Regression (push) Waiting to run
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
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Runtime Broker MariaDB / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
Co-authored-by: Juampi <JuampiHernandez@users.noreply.github.com> |
||
|
|
688f56ed8f
|
fix(serve): report serve.channels restore failures in channel status (#12542)
* fix(serve): report serve.channels restore failures in channel status A channels name that fails to restore never joins the committed selection, so the workspace channel list reported it as stopped and daemon status did not mention it; the failure lived only in the daemon log. Record boot skips and late-restore failures per workspace and channel, redacted the way the log renders them. The management list reports such a channel as error with the recorded lastError, and daemon status raises one channel_restore_failed warning per workspace. A record is dropped when the channel commits, when an operator acts on it or on the whole selection, or when its workspace leaves. * fix(serve): pin the PUT clear and the committed-name filter; drop the commit-time clear Every path that commits a name a restore left down is an operator action that clears the record itself, so the clear on commit was a redundant layer no test could pin. * docs(serve): a restore failure clears on operator action or workspace removal * fix(serve): retry a channel listed as error with nothing running by starting it Web Shell offers Retry for an error runtime and calls restart, which refused a name outside the committed selection with channel_worker_not_enabled. A failed restore now lists that way, and so did a replacement rolled back after its reload failed. Restart of such a name starts it instead. * fix(serve): retire a restore record from current state, not from events Review round 1 found five ways an event-driven record goes wrong: a trust reconcile erased a record that was still true, a workspace removed while its own restore was still on the lane recorded against a workspace that was gone, a later commit of the same name by another workspace left a connected channel reported as unrestored, and a DELETE that stopped nothing cleared everything. Replace the disposal-time clear and the record's clear-on-commit with one read-time rule: a record is dropped when the channel is committed now or its workspace is no longer registered. Reads prune, so nothing leaks. A DELETE clears only when it stopped something, which is the same condition the line above it already uses for channelHostingStoppedByOperator. Also: record a boot worker startup the settings-derived branch swallows, so the shape a single-workspace daemon fails in is reported rather than logged only; key a restore's per-channel errors by channel, since the supervisor stamps them with the owning workspace, not the requesting one; bound the daemon-status message the way the sibling startup-failure surface bounds the same data; carry restore warnings on the bootstrap status response, which is the only one that exists when boot records are written; and drop the record fields no production reader consumes. * docs(serve): state the restore reporting the code makes, and name what stays log-only * test(serve): a swallowed boot startup failure now lists as error, redacted * test(serve): pin the later-commit retire and the cross-owner adapter error * fix(serve): filter stale restore records on read, never delete them The predicate reads live state that can dip: a workspace is briefly absent from the registry while its runtime is replaced. Deleting on a dip would destroy a failure that is still true, which is the defect this surface exists to prevent. * test(serve): pin the boot startup-failure fan-out to every claimant |
||
|
|
f8ce07463b
|
feat(serve): run Qwen Live through the Web Shell, native macOS Host opt-in (#12534)
* feat(serve): run Qwen Live through the Web Shell, native macOS Host opt-in A macOS daemon advertised realtime_voice, registered /live/host, downloaded and launched Qwen Live Host when Live was enabled, and let the native Host supersede a browser endpoint. Keeping two endpoints means two setup flows and two sets of failure modes while Live is experimental, so every platform now uses the Web Shell endpoint (realtime_voice_web, /live/web) by default. The native Host is opt-in with QWEN_LIVE_NATIVE_HOST=1 in the daemon environment (still macOS-only). Without it, /live/setup reports nativeHost: false with a non-retryable install state and never probes for an installed Host, and /live/setup/install and /live/setup/launch answer 409 live_native_host_unavailable, so the Host cannot be installed or started through the API either. The installer and Host code stay in place. Refs #12530 * fix(serve): name the native Live Host switch QWEN_SERVE_LIVE_NATIVE_HOST Triage follow-ups on the web-only Live default: - The switch is read by qwen serve only, so it takes the QWEN_SERVE_ prefix the other serve gates use; QWEN_LIVE_* belongs to the standalone qwen-live daemon and live-host, which do not read it. - The environment variable constant is no longer exported. - A test pins that install/launch report the missing Host even while Live is off. - The qwen serve guide says an already-installed Qwen Live Host cannot connect unless the switch is set. |
||
|
|
69622ba749
|
feat(channels): separate private and group access policies (#12547)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Lint & Static (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / web-shell E2E Smoke (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (no-AK, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (ubuntu-22.04) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (windows-2022) (push) Blocked by required conditions
E2E Tests / Build for E2E (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 1/2 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 2/2 (push) Blocked by required conditions
E2E Tests / E2E Interactive - OpenTUI renderer (bun) (push) Blocked by required conditions
E2E Tests / channel-plugin E2E (nightly) (push) Blocked by required conditions
E2E Tests / cron-interactive E2E (nightly) (push) Blocked by required conditions
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Runtime Broker MariaDB / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com> |
||
|
|
43868d6a69
|
feat(web-shell): add shared URL navigation for pages and sessions (#12499)
* feat(web-shell): add shared URL navigation for pages and sessions * test(web-shell): expect settings route after remote connection return * fix(web-shell): preserve navigation state across workspace flows --------- Co-authored-by: 铃蓝 <guolinliang.gll@alibaba-inc.com> |
||
|
|
126eda5327
|
fix(serve): never hold a workspace registration on the channel-control lane (#12527)
The runtime-added hook awaited `restoreWorkspace()` and `refreshWorkspaces()` on the channel manager. Both run on the manager's single FIFO lane, and the hook runs under the daemon-wide runtime-topology gate that every workspace registration and trust reconcile shares. So a channel worker that never became ready — a peer that accepts TCP and never completes the upgrade — held the lane for the whole channel startup budget, and the next registration waited behind it, holding the gate: a workspace configuring no channels at all took 29.7 s to register instead of 9 ms, measured on a real daemon in #12396's review. Since #12396 that is reachable from a daemon that booted with no channels. The hook now queues both calls and returns; the lane already keeps them in order. That alone would have reopened a race the awaits were hiding — they were also what ordered a second registration's late restore behind the first's commit — so the late restore's delta moves into the lane as well. A new manager operation, `addChannels`, reads the committed selection inside the lane, skips names already committed, leaves a committed `all` alone, and evaluates an optional precondition there too, so an operator's recorded stop binds a restore queued behind it. The late restore uses it instead of reading the selection from outside and replacing it, which makes the local chain #12396 added unnecessary. What changes for a caller: a `201` from `POST /workspaces` no longer means that workspace's channels are up, or that an already-hosted channel it owns has finished restarting. |
||
|
|
b9840886b8
|
feat(core): record when each tool call started (#12517)
Tool timing frames in paged transcript replay carried only a duration: the scheduler logs a batch's tool calls in one loop after every call in it has settled, so a record's event timestamp is the batch's end and no honest per-call start could be derived from it. The start was always measured, though; it just was not kept. Record it as an optional `started_at_ms` on the tool_call event, from the scheduler's terminal states (which now keep `startTime`) and from both places the ACP session logs a tool call, and pass it through unchanged: replay emits it as the tool frame's `startedAt`, the SDK timing reader keeps it, and the trajectory projection puts it on the tool row. Nothing derives it. Records written before this carry none and their frames stay start-less, as before. The trajectory fixture's two tool starts came from a pre-merge build that did derive them, and are removed so the fixture matches what a released daemon sends. |
||
|
|
adb8f5bbd9
|
feat(serve): restore serve.channels once for a workspace registered after boot (#12396)
* feat(serve): let channel grouping take an ownership hint and skip tolerated names `resolveChannelWorkspaceGroups` resolves every selected channel name to the one workspace that owns it, and fails the whole selection when a name has no owner, more than one owner, or an untrusted owner. Both behaviors block a daemon from restoring several workspaces' `serve.channels` at boot: a channel defined in user scope without a `cwd` matches every registered workspace, and one workspace's stale entry would strand every other workspace's channels. Add two optional inputs. `preferredOwners` names the workspace that asked for a channel, and breaks an ambiguous tie in its favor — but only among owners the ownership predicate already accepted, and the owner's trust check still runs afterwards, so a hint can neither invent an owner nor bypass folder trust. `tolerant` marks names whose failure must not fail the selection; those are dropped and reported in `skipped` while every other name groups as usual. Callers that pass neither input get the previous behavior and the previous result shape: `skipped` is omitted when nothing was skipped. * feat(serve): restore every trusted workspace's own serve.channels at boot A flagless `qwen serve` boot read `serve.channels` from the primary workspace alone, so a channel a non-primary workspace was configured to start came back only if an operator called the runtime API again. The management API writes that setting per workspace and reports `startsWithServe` from it, so the toggle in a non-primary workspace promised an automatic start the daemon never performed, and logged nothing when it did not. Build the boot selection from every registered workspace instead: each contributes the list in its own workspace-scope settings, which the fast path already gates on folder trust. The names form one flat selection, as before, plus the attribution the resolver needs. The workspace that listed a name owns it when ownership is otherwise ambiguous, which is what a channel defined in user scope without a `cwd` always is; that hint also travels to runtime resolution, so a later `PUT /workspace/channel` resolves the way boot did. A name a non-primary workspace contributed is dropped, with a log naming it, when it cannot be resolved, and the committed selection is rewritten to what the groups actually host so the lease and `/workspace/channel` agree. The primary workspace's own names keep failing the restore as a whole, which is what a single-workspace daemon does today. `all` stays primary-only: one flat selection cannot express "every channel in A plus two in B", so a primary `all` wins outright and an `all` anywhere else is reported and ignored. Exceeding the channel-control workspace limit still fails the restore loudly rather than hosting a silent subset. * feat(serve): restore serve.channels for a workspace registered after boot A workspace registered through the API after the daemon started kept its channels offline: the runtime-added hook only un-drained a workspace that was already part of the committed selection, and returned immediately when the daemon had no channel worker manager at all. An operator who registered a workspace whose channels are configured to start had to enable them by hand, every time, even though the same settings now bring those channels up when the workspace is present at boot. Read that workspace's own `serve.channels` when it registers and enable the names it asks for, seeding the same ownership attribution the boot restore uses so a definition shared through user scope resolves to the workspace that asked for it. A daemon that was hosting nothing loads the channel runtime and takes the service lease at that point, which is the cost of honoring the setting at all. Two operator decisions outrank the setting, and neither could be inferred from the committed selection: removing a workspace also drops its names, so a selection diff would read "removed" as "stopped on purpose" and would refuse to restore exactly the workspace re-registration this is meant to serve. Instead the management service reports an explicit runtime intent when a channel is started or stopped, and the daemon-wide `DELETE /workspace/channel` records that hosting is off until something enables it again. Both live only in this process: the next boot reads the settings from disk, which is what the setting means. * fix(serve): answer a registration before its channels, and keep every hint Review of the late-registration restore found three ways it did not hold up, and the sandboxed run measured a fourth. Ownership hints lived in one map that `onCommittedSelection` rebuilt from the committed groups on every commit. A workspace listing two channels that only its own request can disambiguate therefore lost the second name's hint as soon as the first name committed, and the second failed as ambiguous — silently, because the registration had already answered. Hints now come from two sources: what the settings asked for, which accumulates and is never rebuilt, and what the daemon committed, which still tracks the groups. Resolution reads both, so a name that is not committed yet still resolves. That also settles the case where a channel started by hand after a daemon-wide stop could not be resolved until the daemon restarted. An explicit `--channel` selection bounded what the daemon hosted at boot but not afterwards, so a workspace registered later could add channels the operator never selected. It now bounds the daemon's whole life, which is what the protocol documentation already said. `startupChannelsForWorkspace` assumed every activation was non-primary, but activation also fires for the primary workspace when its trust is re-materialized — enough to print a banner claiming that workspace "was not restored". The flag is passed through instead. Finally, the restore ran inside the runtime-added hook, which holds the daemon-wide runtime-topology gate. A channel endpoint that accepts TCP and never completes the handshake made `POST /workspaces` take 30s, and — measured in the sandboxed verification — held an unrelated registration of a workspace with no channels for 29.9s behind the same gate, accumulating per workspace. Bringing channels up is not part of registering a workspace, so it now runs after the response, with the ownership seeding still synchronous because a racing runtime resolution has to see it. One name that cannot be hosted no longer strands the rest of that workspace's list either. Every fix is covered by a test that fails without it, and the two re-enable paths the mutation matrix found untested — `PUT /workspace/channel` and a per-channel start — now each have one. * feat(serve): restore serve.channels for a workspace registered after boot A workspace registered through the API after the daemon started kept its channels offline: the runtime-added hook only un-drained a workspace that was already part of the committed selection, and returned immediately when the daemon had no channel worker manager at all. An operator who registered a workspace whose channels are configured to start had to enable them by hand, every time, even though the same settings now bring those channels up when the workspace is present at boot. Read that workspace's own `serve.channels` when it registers and enable the names it asks for, recording what it asked for in the boot hint layer so every name keeps its ownership hint through the commits the names before it cause. A daemon that was hosting nothing loads the channel runtime and takes the service lease at that point, which is the cost of honoring the setting at all. Bringing those channels up is not part of registering a workspace. The hook runs under the daemon-wide runtime-topology gate, so awaiting a worker there holds that gate for as long as the worker takes to become ready — up to the 30s startup budget — and every other registration and trust reconcile queues behind it, including workspaces that configure no channels at all. The restore now runs after the registration answers, with the ownership record still written synchronously because a racing runtime resolution has to see it, and one name that cannot be hosted no longer strands the rest of that workspace's list. An explicit `--channel` selection bounds what the daemon hosts for its whole life, so a workspace registered later adds nothing to it, which is what the protocol documentation already said. Activation also fires for the primary workspace when its trust is re-materialized, so the primary flag is passed through rather than assumed; a primary `all` is no longer reported as a workspace that "was not restored". Two operator decisions outrank the setting, and neither could be inferred from the committed selection: removing a workspace also drops its names, so a selection diff would read "removed" as "stopped on purpose" and would refuse to restore exactly the workspace re-registration this is meant to serve. Instead the management service reports an explicit runtime intent when a channel is started or stopped, and the daemon-wide `DELETE /workspace/channel` records that hosting is off until something enables it again. Both live only in this process: the next boot reads the settings from disk, which is what the setting means. * fix(serve): never let a later registration claim a channel someone else hosts The late-registration restore recorded an ownership hint for every name the arriving workspace listed, overwriting whatever the hint already said, and wrote it on top of the layer the committed groups own instead of under it. Both halves are wrong, and together they move a running channel. `startupChannelsForWorkspace` resolves one workspace at a time, so every name that workspace lists comes back hinted — the multiple-claim case the resolver detects at boot cannot arise inside a single-workspace resolution. A channel defined once in user scope with no `cwd` is owned by every registered workspace, which is exactly what the per-workspace startup toggle produces, so a second workspace listing that name would rewrite the hint to itself. The reload this hook triggers immediately afterwards re-resolves the committed selection with the new hint, and reconcile then stops the worker where it is running and starts it under the newcomer. Registering an unrelated workspace was enough to take another workspace's channel. A second claim now withdraws the hint instead of overwriting it, which is what `resolveChannelWorkspaceGroups` documents as the caller's obligation: one map holds one owner per name, so the only honest answer to two claimants is no hint, leaving the channel config and the groups the daemon already committed to decide. That withdrawal is logged. Hint rebuilding also moves into one helper both writers share, so the committed groups always layer over the boot hints rather than the other way round. * refactor(serve): narrow the late-registration restore to one mechanism The review of the previous revision found twelve critical defects, and almost all of them were consequences of machinery this PR had added rather than of the feature itself: an ownership hint written and withdrawn at runtime, two operator-intent markers with different scopes than the actions that set them, and a hand-rolled detached task that bypassed the drain handling the runtime route already has. Patching each one had produced the next, so this replaces the machinery instead of extending it. A workspace registered after boot now restores its own `serve.channels` exactly once per daemon run, and that single rule replaces both markers. Activation fires again when a workspace is removed and registered back, and when its trust is re-materialized; restoring there is what would undo an operator who stopped one of those channels in between, which is the case #11452 asks this not to do. Recording the workspace rather than each channel's runtime state removes the `onRuntimeIntent` hook, the daemon-wide stop marker whose scope did not match the per-workspace action that cleared it, and the marker that was cleared before the selection it described had committed. The restore no longer writes ownership hints. Resolving one workspace at a time made every name it listed look unambiguously its own, which is precisely the multiple-claim case `resolveChannelWorkspaceGroups` documents as the one a caller must not hint; withdrawing the hint afterwards then broke the protocol's promise that boot attribution lasts as long as the daemon runs. Without hints a name this daemon cannot attribute simply is not restored late, with the error logged, and the sentence about durable boot attribution stays true. The names join the committed selection through `setChannelWorkerSelection`, the same funnel the runtime route uses, in one change rather than one per name. That inherits the draining checks — a restore in flight when the daemon starts closing now refuses instead of starting a worker behind the shutdown — and removes k-1 selection commits, each of which re-resolved every group. A committed `all` selection is left alone, because one flat selection cannot express "everything the primary workspace has, plus these two". Settings are read from disk at activation rather than from the boot snapshot, and the primary workspace is left to the boot path, so a trust re-materialization no longer reports it as a workspace that was not restored. * fix(serve): count a registration whose names were all hosted as its one restore The once-per-daemon record was written only on the branch that proposed a restore. A workspace whose every listed name was already hosted returned at the empty `pending` check before being recorded, so it could come back later — after an operator stopped one of those names — and bring the stopped name up again, which is the exact thing the record exists to prevent. Record the workspace as soon as it has asked for anything, before the hosted names are filtered out. A workspace with no `serve.channels` still returns before the record, so one that gains a list later still restores when it registers again. The fix and its regression test are from the maintainer verification on the PR, which reproduced the revival on a real daemon; this test is also the one that pins the record itself, since removing it no longer turns the single-channel once-per-daemon case red once the stopped-hosting guard fires first. Also render `requestedByWorkspace` in the boot restore log as `name@workspace` pairs: the daemon log formats every field with String(), so the map I passed in #12385 printed as "[object Object]". * fix(serve): record an operator's stop explicitly and run late restores one at a time Two defects the last revision introduced, both measured on a real daemon by the round-3 review. The late restore inferred "an operator stopped channel hosting" from a channel manager that exists and reports `enabled: false`. That state is not specific to a stop. Listing channels — a read, which the Web Shell does on its channel page — creates the manager, and a manager that has never hosted anything reports exactly the same `enabled: false, selection: null` as a stopped one; so does a manager whose own late restore just failed. On the daemon this feature exists for, one status read switched the restore off for the rest of its life, and the log blamed a stop nobody performed. The stop is now recorded where it happens: `DELETE /workspace/channel` sets it only when it actually stopped something, and it is cleared only after a `PUT /workspace/channel` commits, so a selection that fails leaves hosting as stopped as it was. Each late restore replaced the whole committed selection with the one it read at hook time. On a daemon that has just booted with no channels, no manager exists yet to order two registrations, so the second read the selection before the first committed and its commit dropped the first workspace's channels. Late restores now run one at a time, each reading the committed selection when its turn comes. The failure log for a late restore now redacts credentials the way the boot restore's does: the message can carry an adapter's own error text. The documentation is corrected where it had overstated things: registering a workspace with `serve.channels` is a third way to load the channel runtime and reserve the lease; the restore detaches from the registration, but the hook's existing bookkeeping on the channel-control lane can still delay other registrations for up to the startup budget; and unlike boot, one unhostable name costs a late workspace its whole list. Both design documents state the same rule again. * fix(serve): name the workspace in a late restore's failure record A failed late restore was the one record in its block without a workspace, so when two workspaces registered close together the error could only be attributed by its position in the log — and two workspaces listing the same name produce identical error text. Pass the workspace as the same structured field its sibling records carry. |
||
|
|
5feb7a4f34
|
feat(serve): Add opt-in fixed ACP child heap enforcement (#12353)
* feat(serve): Add opt-in fixed ACP child heap enforcement Apply the boot-resolved fixed partition to managed ACP children while reusing shared count admission and idle reclamation. Preserve legacy modes, validate inherited heap options, and report enforcement only for managed wiring. Refs #8182 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): Reuse resolved ACP child environment Keep the standalone process-environment fallback at a single guarded access while using the same environment for heap validation and child spawning. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): Align heap enforcement guidance Pin NODE_OPTIONS rewriting to Node itself, correct stale CLI/status wording, and document the existing watcher-gated heap telemetry used for rollout calibration. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
ec109102e0
|
feat(daemon): Add batched workspace session catalogs (#12254)
* feat(daemon): Add batched workspace session catalogs * test(serve): include batch catalog in capability expectations * fix(daemon): address batch catalog review feedback * fix(daemon): retain catalog reads across policy revisions * fix(daemon): bind session cursors to catalog read modes --------- Co-authored-by: yungsen.hys <yungsen.hys@alibaba-inc.com> |
||
|
|
e1213d57d9
|
feat(serve): restore every trusted workspace's own serve.channels at boot (#12385)
* feat(serve): let channel grouping take an ownership hint and skip tolerated names `resolveChannelWorkspaceGroups` resolves every selected channel name to the one workspace that owns it, and fails the whole selection when a name has no owner, more than one owner, or an untrusted owner. Both behaviors block a daemon from restoring several workspaces' `serve.channels` at boot: a channel defined in user scope without a `cwd` matches every registered workspace, and one workspace's stale entry would strand every other workspace's channels. Add two optional inputs. `preferredOwners` names the workspace that asked for a channel, and breaks an ambiguous tie in its favor — but only among owners the ownership predicate already accepted, and the owner's trust check still runs afterwards, so a hint can neither invent an owner nor bypass folder trust. `tolerant` marks names whose failure must not fail the selection; those are dropped and reported in `skipped` while every other name groups as usual. Callers that pass neither input get the previous behavior and the previous result shape: `skipped` is omitted when nothing was skipped. * feat(serve): restore every trusted workspace's own serve.channels at boot A flagless `qwen serve` boot read `serve.channels` from the primary workspace alone, so a channel a non-primary workspace was configured to start came back only if an operator called the runtime API again. The management API writes that setting per workspace and reports `startsWithServe` from it, so the toggle in a non-primary workspace promised an automatic start the daemon never performed, and logged nothing when it did not. Build the boot selection from every registered workspace instead: each contributes the list in its own workspace-scope settings, which the fast path already gates on folder trust. The names form one flat selection, as before, plus the attribution the resolver needs. The workspace that listed a name owns it when ownership is otherwise ambiguous, which is what a channel defined in user scope without a `cwd` always is; that hint also travels to runtime resolution, so a later `PUT /workspace/channel` resolves the way boot did. A name a non-primary workspace contributed is dropped, with a log naming it, when it cannot be resolved, and the committed selection is rewritten to what the groups actually host so the lease and `/workspace/channel` agree. The primary workspace's own names keep failing the restore as a whole, which is what a single-workspace daemon does today. `all` stays primary-only: one flat selection cannot express "every channel in A plus two in B", so a primary `all` wins outright and an `all` anywhere else is reported and ignored. Exceeding the channel-control workspace limit still fails the restore loudly rather than hosting a silent subset. * fix(serve): keep boot channel owner hints across runtime commits Three review follow-ups to the multi-workspace startup restore. `onCommittedSelection` rebuilt the owner hints from the committed groups alone, so a name a commit did not carry lost its boot-time attribution for good: stopping a user-scope channel and enabling it again resolved against every registered workspace and failed as `ambiguous_channel_workspace` until the daemon restarted, which is what that hint exists to prevent. Keep the boot hints as a layer the committed groups override. A hint naming a workspace that is no longer registered is inert, since resolution only takes a hint matching one of the live owners. Tolerance leaked to names the primary workspace listed: `claims.some(...)` made a name droppable as soon as any other workspace listed it too, so a second workspace copying `serve.channels` silently downgraded the primary's fail-fast restore to a logged skip. Key it on the absence of a primary claim, which is what the protocol doc already describes. The `claimed_by_multiple_workspaces` diagnostic wrote a boot banner for a healthy configuration — one channel, an explicit `cwd`, listed by two workspaces, which is exactly what the per-workspace toggle produces. It stays in the daemon log and out of stderr; a resolution that actually fails still reports itself. The remaining gap — no hint at all when the primary, or two workspaces, claim a user-scope name — is an ownership policy decision rather than a fix, and is tracked in #12402. |
||
|
|
3dc9778dea
|
perf(cli): optimize one-shot headless execution (#12340)
* perf(cli): optimize one-shot headless execution * fix(cli): preserve unset shell schema default * fix(cli): fall back when process replacement fails * fix(cli): keep relaunch supervisor for headless slash-command prompts Process replacement leaves no supervisor to consume exit code 43, so a headless `/update` would exit having installed nothing. Keep the parent process when the one-shot prompt dispatches as a slash command, and pin both directions in llm.test.tsx. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): update settings dialog snapshots for mode-based PTY default With no fixed schema default the dialog now renders "(not set)" for Interactive Shell (PTY) instead of true; refresh the ten snapshots. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(cli): document process-replacement supervisor and reset semantics Explain at the execve call why afterSpawn has nothing to protect there, and record in the design doc that onUpdateRelaunch intentionally never fires on the replacement path and that "reset to default" now clears the interactive-shell key instead of writing true. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * Revert "fix(cli): keep relaunch supervisor for headless slash-command prompts" The guarded path is unreachable: both relaunchForUpdate() call sites sit inside `if (context.executionMode === 'interactive' && projectRoot)` (update-command.ts:89), and a one-shot prompt resolves executionMode to 'non_interactive' (config.ts:1837-1839 -> nonInteractiveCliCommands.ts:431), so a headless `/update` updates standalone installs in-process or prints manual instructions and never exits 43. Per review correction, drop the guard and its test rather than pin a non-bug. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): pin stream-json shell default; document schema default contract Add the missing --input-format stream-json leg to the shell-default matrix, and note at the SettingDefinition declaration that `default` is required for type inference even when its runtime value is undefined. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): pin the full replaceProcess predicate for one-shot launches The verify lane's mutation run left M7 (jsonFd term) surviving and the broader predicate shape unpinned after the slash-command guard was reverted. Assert the replaceProcess value relaunchAppInChildProcess receives across the launch modes: plain and slash-command one-shot prompts replace the process; ACP, -i, file, json-fd, stream-json, and dual-output file inputs keep the supervisor. Mutation-checked: deleting the jsonFd term fails the json-fd row, forcing the expression true fails all six supervisor rows. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmuakc4l1ae * style(cli): prettier-format the replaceProcess predicate test The Lint & Static lane's Run Prettier step failed on the test file pushed in 22dd3240aa. Format-only change, no semantics. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmuakc4l1ae * fix(cli): surface execve fallback reason and pin no-prompt predicate * refactor(cli): name the one-shot prompt predicate and drop redundant pins --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
065dd351c8
|
fix(serve): tell clients which history entries cannot be retried (#12349)
* fix(serve): tell clients which history entries cannot be retried A retry or rerun from history is refused when the run's history does not have the args to start it with -- they were too large to keep, or the snapshot predates keeping them and so cannot say whether there were any. Only the first of those reached the wire, as `argsOmitted`, so a client rendered a Retry for the second that the call always refused. Both now project as `argsUnavailable` on the task, from the same predicate the daemon refuses on, so the two cannot drift apart. `argsOmitted` stays beside it as the reason a client can name. The args themselves still never leave the daemon. * fix(serve): keep argsUnavailable's literal type through the projection A plain `true` in the ternary widened to `boolean`, which the wire type does not accept, and the consistency test handed the predicate a fixture typed loosely enough that its argument did not match either. Both were type errors only; the behaviour and every assertion are unchanged. * fix(cli): ask for a resumed run's args when its history cannot name them The interrupted-run notice hands the user a call to paste, and nothing on that path refuses it the way the daemon refuses a history retry. It asked for the original args only when they had been too large to keep, so a run recorded before args were kept got a call that looks complete and would resume with none -- replaying nothing and re-dispatching every agent under the old run id. It now reads the same predicate the refusal and the projection read, so all three agree about one snapshot. The protocol's table row says both cases too, rather than only the one it happened to be written for. * fix(workflows): claim a run that had no args as one that had none `argsRecorded` arrived with the wire marker, so a checkpoint written by v0.24.1 does not carry it. Claiming such a run produced a snapshot with no `args`, no `argsOmitted` and no `argsRecorded` -- which reads as "nobody knows whether this run had args". The run then lost its retry, was marked `argsUnavailable` on the wire, and its resume notice asked the user for args it never had. Every checkpoint format has recorded the run's args, so a checkpoint carrying neither them nor `argsOmitted` is a run that had none. Say so when claiming it. Two more from the same review: The panel withholds Retry and Rerun for a run whose args its history cannot supply, which also took away the only place the daemon's reason appeared -- it arrived by pressing Retry and being refused. It now says why, without the failed round trip. The consistency test asserted the predicate the handler itself calls while claiming to assert the wire. Its name now says what it checks, and the projection's own suite pins the other end against the same exported predicate rather than restating it. The `invalidParams` stub is installed only in the branch that consumes it. * style(docs): re-pad the workflow refusal table after the merge |